using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Reflection; using System.Text; using Umbraco.Core.Logging; namespace Umbraco.Core.Dynamics { /// /// A helper class to try invoke members, find properties, etc... /// internal class DynamicInstanceHelper { internal class TryInvokeMemberResult { public object ObjectResult { get; private set; } public TryInvokeMemberSuccessReason Reason { get; private set; } public TryInvokeMemberResult(object result, TryInvokeMemberSuccessReason reason) { ObjectResult = result; Reason = reason; } } internal enum TryInvokeMemberSuccessReason { FoundProperty, FoundMethod, FoundExtensionMethod } /// /// Attempts to invoke a member based on the dynamic instance /// /// /// The object instance to invoke the extension method for /// /// /// /// /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method /// on the object that matches the binder name /// public static Attempt TryInvokeMember(T thisObject, InvokeMemberBinder binder, object[] args) { return TryInvokeMember(thisObject, binder, args, null); } /// /// Attempts to invoke a member based on the dynamic instance /// /// /// The object instance to invoke the extension method for /// /// /// The types to scan for extension methods /// /// /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method /// on the object that matches the binder name, if that fails it will then attempt to invoke an extension method /// based on the binder name and the extension method types to scan. /// public static Attempt TryInvokeMember(T thisObject, InvokeMemberBinder binder, object[] args, IEnumerable findExtensionMethodsOnTypes) { //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods! object result; try { //Property? result = typeof(T).InvokeMember(binder.Name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.GetProperty, null, thisObject, args); return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); } catch (MissingMethodException) { try { //Static or Instance Method? result = typeof(T).InvokeMember(binder.Name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.InvokeMethod, null, thisObject, args); return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); } catch (MissingMethodException) { if (findExtensionMethodsOnTypes != null) { try { result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes); return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); } catch (TargetInvocationException ext) { //don't log here, we return this exception because the caller may need to do something specific when //this exception occurs. return new Attempt(ext); } catch (Exception ex) { var sb = new StringBuilder("An error occurred finding an executing an extension method for type "); sb.Append(typeof (T)); sb.Append("Types searched for extension methods were "); foreach(var t in findExtensionMethodsOnTypes) { sb.Append(t + ","); } LogHelper.Error(sb.ToString(), ex); return new Attempt(ex); } } return Attempt.False; } } catch (Exception ex) { LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); return new Attempt(ex); } } /// /// Attempts to find an extension method that matches the name and arguments based on scanning the Type's passed in /// to the findMethodsOnTypes parameter /// /// The instance object to execute the extension method for /// /// /// /// internal static object FindAndExecuteExtensionMethod(T thisObject, object[] args, string name, IEnumerable findMethodsOnTypes) { object result = null; //find known extension methods that match the first type in the list MethodInfo toExecute = null; foreach (var t in findMethodsOnTypes) { toExecute = ExtensionMethodFinder.FindExtensionMethod(t, args, name, false); if (toExecute != null) break; } if (toExecute != null) { var genericArgs = (new[] { (object)thisObject }).Concat(args); result = toExecute.Invoke(null, genericArgs.ToArray()); } else { throw new MissingMethodException(); } return result; } } }