using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Reflection; using System.Text; using Umbraco.Core.Cache; 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(IRuntimeCacheProvider runtimeCache, T thisObject, InvokeMemberBinder binder, object[] args) { return TryInvokeMember(runtimeCache, 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( IRuntimeCacheProvider runtimeCache, 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, BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, null, thisObject, args); return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); } catch (MissingMethodException) { try { //Static or Instance Method? result = typeof(T).InvokeMember(binder.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, thisObject, args); return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); } catch (MissingMethodException) { if (findExtensionMethodsOnTypes != null) { try { result = FindAndExecuteExtensionMethod(runtimeCache, thisObject, args, binder.Name, findExtensionMethodsOnTypes); return Attempt.Succeed(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. var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); return Attempt.Fail(mresult, ext); } catch (Exception ex) { var sb = new StringBuilder(); sb.AppendFormat("An error occurred finding and executing extension method \"{0}\" ", binder.Name); sb.AppendFormat("for type \"{0}\". ", typeof (T)); sb.Append("Types searched for extension methods were "); sb.Append(string.Join(", ", findExtensionMethodsOnTypes)); sb.Append("."); LogHelper.Error(sb.ToString(), ex); var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); return Attempt.Fail(mresult, ex); } } return Attempt.Fail(); } } catch (Exception ex) { LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); return Attempt.Fail(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( IRuntimeCacheProvider runtimeCache, 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(runtimeCache, t, args, name, false); if (toExecute != null) break; } if (toExecute != null) { var genericArgs = (new[] { (object)thisObject }).Concat(args); // else we'd get an exception w/ message "Late bound operations cannot // be performed on types or methods for which ContainsGenericParameters is true." // because MakeGenericMethod must be used to obtain an actual method that can run if (toExecute.ContainsGenericParameters) throw new InvalidOperationException("Method contains generic parameters, something's wrong."); result = toExecute.Invoke(null, genericArgs.ToArray()); } else { throw new MissingMethodException(typeof(T).FullName, name); } return result; } } }