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;
}
}
}