Fixes: #U4-1207, Enhancements to DynamicXml (legacy and new), now implements proper IEnumerable<T> interfaces.

Added DynamicInstanceHelper for any dynamic object that should find matches on methods, properties and
extension methods. Fixed ExtensionMethodFinder to find the Enumerable extensions (was previously set to IEnumerable which would
find nothing).
This commit is contained in:
Shannon Deminick
2012-11-18 10:28:53 +05:00
parent 4683144987
commit 48c45447e4
9 changed files with 418 additions and 241 deletions

View File

@@ -0,0 +1,178 @@
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
{
/// <summary>
/// A helper class to try invoke members, find properties, etc...
/// </summary>
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
}
/// <summary>
/// Attempts to invoke a member based on the dynamic instance
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="thisObject">The object instance to invoke the extension method for</param>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <remarks>
/// 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
/// </remarks>
public static Attempt<TryInvokeMemberResult> TryInvokeMember<T>(T thisObject, InvokeMemberBinder binder, object[] args)
{
return TryInvokeMember<T>(thisObject, binder, args, null);
}
/// <summary>
/// Attempts to invoke a member based on the dynamic instance
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="thisObject">The object instance to invoke the extension method for</param>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="findExtensionMethodsOnTypes">The types to scan for extension methods </param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public static Attempt<TryInvokeMemberResult> TryInvokeMember<T>(T thisObject,
InvokeMemberBinder binder,
object[] args,
IEnumerable<Type> 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<TryInvokeMemberResult>(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<TryInvokeMemberResult>(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod));
}
catch (MissingMethodException)
{
if (findExtensionMethodsOnTypes != null)
{
try
{
result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes);
return new Attempt<TryInvokeMemberResult>(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<TryInvokeMemberResult>(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<DynamicInstanceHelper>(sb.ToString(), ex);
return new Attempt<TryInvokeMemberResult>(ex);
}
}
return Attempt<TryInvokeMemberResult>.False;
}
}
catch (Exception ex)
{
LogHelper.Error<DynamicInstanceHelper>("An unhandled exception occurred in method TryInvokeMember", ex);
return new Attempt<TryInvokeMemberResult>(ex);
}
}
/// <summary>
/// Attempts to find an extension method that matches the name and arguments based on scanning the Type's passed in
/// to the findMethodsOnTypes parameter
/// </summary>
/// <param name="thisObject">The instance object to execute the extension method for</param>
/// <param name="args"></param>
/// <param name="name"></param>
/// <param name="findMethodsOnTypes"></param>
/// <returns></returns>
internal static object FindAndExecuteExtensionMethod<T>(T thisObject,
object[] args,
string name,
IEnumerable<Type> 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;
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Collections;
@@ -10,7 +11,7 @@ using System.Web;
namespace Umbraco.Core.Dynamics
{
public class DynamicXml : DynamicObject, IEnumerable
public class DynamicXml : DynamicObject, IEnumerable<DynamicXml>, IEnumerable<XElement>
{
public XElement BaseElement { get; set; }
@@ -70,7 +71,51 @@ namespace Umbraco.Core.Dynamics
HandleIEnumerableXElement(elements, out result);
return true; //anyway
}
return base.TryInvokeMember(binder, args, out result);
//ok, now lets try to match by member, property, extensino method
var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[]
{
typeof (IEnumerable<XElement>),
typeof (DynamicXml)
});
if (attempt.Success)
{
result = attempt.Result.ObjectResult;
//need to check the return type and possibly cast if result is from an extension method found
if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod)
{
if (attempt.Result.ObjectResult != null)
{
if (attempt.Result.ObjectResult is XElement)
{
result = new DynamicXml((XElement) attempt.Result.ObjectResult);
}
if (attempt.Result.ObjectResult is IEnumerable<XElement>)
{
result = ((IEnumerable<XElement>) attempt.Result.ObjectResult).Select(x => new DynamicXml(x));
}
if (attempt.Result.ObjectResult is IEnumerable<DynamicXml>)
{
result = ((IEnumerable<DynamicXml>)attempt.Result.ObjectResult).Select(x => new DynamicXml(x.BaseElement));
}
}
}
return true;
}
//this is the result of an extension method execution gone wrong so we return dynamic null
if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod
&& attempt.Error != null && attempt.Error is TargetInvocationException)
{
result = new DynamicNull();
return true;
}
result = null;
return false;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
@@ -181,20 +226,41 @@ namespace Umbraco.Core.Dynamics
return new DynamicXml(this.BaseElement.XPathSelectElements(expression).FirstOrDefault());
}
public IEnumerator GetEnumerator()
{
return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator();
}
IEnumerator<XElement> IEnumerable<XElement>.GetEnumerator()
{
return this.BaseElement.Elements().GetEnumerator();
}
public IEnumerator<DynamicXml> GetEnumerator()
{
return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count()
{
return this.BaseElement.Elements().Count();
return ((IEnumerable<XElement>)this).Count();
}
public bool Any()
{
return this.BaseElement.Elements().Any();
return ((IEnumerable<XElement>)this).Any();
}
public IEnumerable<DynamicXml> Take(int count)
{
return ((IEnumerable<DynamicXml>)this).Take(count);
}
public IEnumerable<DynamicXml> Skip(int count)
{
return ((IEnumerable<DynamicXml>)this).Skip(count);
}
public bool IsNull()
{
return false;
@@ -514,7 +580,7 @@ namespace Umbraco.Core.Dynamics
}
public IEnumerable<DynamicXml> Ancestors(Func<XElement, bool> func)
{
List<XElement> ancestorList = new List<XElement>();
var ancestorList = new List<XElement>();
var node = this.BaseElement;
while (node != null)
{
@@ -661,5 +727,7 @@ namespace Umbraco.Core.Dynamics
}
}
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Umbraco.Core.Dynamics
);
//add the extension methods defined in IEnumerable
candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public));
candidates = candidates.Concat(typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public));
//filter by name
var methodsByName = candidates.Where(m => m.Name == name);

View File

@@ -65,6 +65,7 @@
<Compile Include="DictionaryExtensions.cs" />
<Compile Include="Dictionary\CultureDictionaryFactoryResolver.cs" />
<Compile Include="Dictionary\ICultureDictionaryFactory.cs" />
<Compile Include="Dynamics\DynamicInstanceHelper.cs" />
<Compile Include="Enum.cs" />
<Compile Include="HashCodeCombiner.cs" />
<Compile Include="IO\FileSystemWrapper.cs" />