using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Dynamic; using umbraco.interfaces; using System.Collections; using System.Reflection; using System.Runtime.CompilerServices; using System.Web.Compilation; using System.Linq.Expressions; using System.Linq.Dynamic; namespace umbraco.MacroEngines { public class DynamicNodeList : DynamicObject, IEnumerable { public IEnumerable Items { get; set; } public DynamicNodeList() { Items = new List(); } public DynamicNodeList(IEnumerable items) { List list = items.ToList(); list.ForEach(node => node.ownerList = this); Items = list; } public DynamicNodeList(IEnumerable items) { List list = items.Select(x => new DynamicNode(x)).ToList(); list.ForEach(node => node.ownerList = this); Items = list; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { var name = binder.Name; if (name == "Where") { string predicate = args.First().ToString(); var values = args.Skip(1).ToArray(); result = new DynamicNodeList(this.Where(predicate, values).ToList()); return true; } if (name == "OrderBy") { result = new DynamicNodeList(this.OrderBy(args.First().ToString()).ToList()); return true; } try { //Property? result = Items.GetType().InvokeMember(binder.Name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetProperty, null, Items, args); return true; } catch (MissingMethodException) { try { //Static or Instance Method? result = Items.GetType().InvokeMember(binder.Name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.InvokeMethod, null, Items, args); return true; } catch (MissingMethodException) { try { result = ExecuteExtensionMethod(args, name); return true; } catch (TargetInvocationException) { //We do this to enable error checking of Razor Syntax when a method e.g. ElementAt(2) is used. //When the Script is tested, there's no Children which means ElementAt(2) is invalid (IndexOutOfRange) //Instead, we are going to return an empty DynamicNode. result = new DynamicNode(); return true; } catch { result = null; return false; } } } catch { result = null; return false; } } List GetAllExtensionMethods(Type[] genericParameterTypeList, Type explicitTypeToSearch, string name, int argumentCount) { //get extension methods from runtime var candidates = ( from assembly in BuildManager.GetReferencedAssemblies().Cast() where assembly.IsDefined(typeof(ExtensionAttribute), false) from type in assembly.GetTypes() where (type.IsDefined(typeof(ExtensionAttribute), false) && type.IsSealed && !type.IsGenericType && !type.IsNested) from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) // this filters extension methods where method.IsDefined(typeof(ExtensionAttribute), false) select method ); //search an explicit type (e.g. Enumerable, where most of the Linq methods are defined) if (explicitTypeToSearch != null) { candidates = candidates.Concat(explicitTypeToSearch.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); } //filter by name var methodsByName = candidates.Where(m => m.Name == name); var isGenericAndRightParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + 1); //find the right overload that can take genericParameterType //which will be either DynamicNodeList or List which is IEnumerable` var withGenericParameterType = isGenericAndRightParamCount.Select(m => new { m, t = firstParameterType(m) }); var methodsWhereArgZeroIsTargetType = (from method in withGenericParameterType where method.t != null && methodArgZeroHasCorrectTargetType(method.m, method.t, genericParameterTypeList) select method); return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToList(); } private bool methodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) { //This is done with seperate method calls because you can't debug/watch lamdas - if you're trying to figure //out why the wrong method is returned, it helps to be able to see each boolean result return // is it defined on me? methodArgZeroHasCorrectTargetType_TypeMatchesExactly(method, firstArgumentType, genericParameterTypeList) || // or on any of my interfaces? methodArgZeroHasCorrectTargetType_AnInterfaceMatches(method, firstArgumentType, genericParameterTypeList) || // or on any of my base types? methodArgZeroHasCorrectTargetType_IsASubclassOf(method, firstArgumentType, genericParameterTypeList) || //share a common interface (e.g. IEnumerable) methodArgZeroHasCorrectTargetType_ShareACommonInterface(method, firstArgumentType, genericParameterTypeList); } private static bool methodArgZeroHasCorrectTargetType_ShareACommonInterface(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) { Type[] interfaces = firstArgumentType.GetInterfaces(); if (interfaces.Length == 0) { return false; } bool result = interfaces.All(i => genericParameterTypeList.Any(gt => gt.GetInterfaces().Contains(i))); return result; } private static bool methodArgZeroHasCorrectTargetType_IsASubclassOf(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) { bool result = genericParameterTypeList.Any(gt => gt.IsSubclassOf(firstArgumentType)); return result; } private static bool methodArgZeroHasCorrectTargetType_AnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) { bool result = genericParameterTypeList.Any(gt => gt.GetInterfaces().Contains(firstArgumentType)); return result; } private static bool methodArgZeroHasCorrectTargetType_TypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) { bool result = genericParameterTypeList.Any(gt => gt == firstArgumentType); return result; } private Type firstParameterType(MethodInfo m) { ParameterInfo[] p = m.GetParameters(); if (p.Count() > 0) { return p.First().ParameterType; } return null; } private object ExecuteExtensionMethod(object[] args, string name) { object result; //Extension method Type tObject = Items.GetType(); Type t = tObject.GetGenericArguments()[0]; var methods = GetAllExtensionMethods(new Type[] { typeof(DynamicNodeList), tObject }, typeof(Enumerable), name, args.Length); if (methods.Count == 0) { throw new MissingMethodException(); } MethodInfo firstMethod = methods.First(); // NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods if (methods.Count > 1) firstMethod = methods.First(x => x.IsGenericMethodDefinition); MethodInfo methodToExecute = null; if (firstMethod.IsGenericMethodDefinition) { methodToExecute = firstMethod.MakeGenericMethod(t); } else { methodToExecute = firstMethod; } if (methodToExecute.GetParameters().First().ParameterType == typeof(DynamicNodeList)) { var genericArgs = (new[] { this }).Concat(args); result = methodToExecute.Invoke(null, genericArgs.ToArray()); } else { var genericArgs = (new[] { Items }).Concat(args); result = methodToExecute.Invoke(null, genericArgs.ToArray()); } if (result is IEnumerable) { result = new DynamicNodeList((IEnumerable)result); } if (result is IEnumerable) { result = new DynamicNodeList((IEnumerable)result); } if (result is INode) { result = new DynamicNode((INode)result); } return result; } public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } public IQueryable Where(string predicate, params object[] values) { return ((IQueryable)Items.AsQueryable()).Where(predicate, values); } public IQueryable OrderBy(string key) { return ((IQueryable)Items.AsQueryable()).OrderBy(key); } } }