From 64c9d97e53d16d7e4e837e6265ea015d128cf284 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Fri, 24 Aug 2012 23:44:47 +0700 Subject: [PATCH] Got extension methods working on DynamicDocument/DynamicDocumentList and added unit tests for them. --- src/Umbraco.Core/Dynamics/DynamicDocument.cs | 5 +- .../Dynamics/DynamicDocumentList.cs | 4 +- .../Dynamics/ExtensionMethodFinder.cs | 49 ++++++++++--------- src/Umbraco.Tests/DynamicDocumentTests.cs | 39 ++++++++++++++- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Dynamics/DynamicDocument.cs b/src/Umbraco.Core/Dynamics/DynamicDocument.cs index 9a07d5715c..50a474be6c 100644 --- a/src/Umbraco.Core/Dynamics/DynamicDocument.cs +++ b/src/Umbraco.Core/Dynamics/DynamicDocument.cs @@ -252,6 +252,8 @@ namespace Umbraco.Core.Dynamics /// public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { + //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods! + try { //Property? @@ -294,6 +296,8 @@ namespace Umbraco.Core.Dynamics catch { + //TODO: LOg this! + result = null; return false; } @@ -316,7 +320,6 @@ namespace Umbraco.Core.Dynamics var methodTypesToFind = new[] { - typeof(IDocument), typeof(DynamicDocument) }; diff --git a/src/Umbraco.Core/Dynamics/DynamicDocumentList.cs b/src/Umbraco.Core/Dynamics/DynamicDocumentList.cs index 8b37f007fe..54bd02689a 100644 --- a/src/Umbraco.Core/Dynamics/DynamicDocumentList.cs +++ b/src/Umbraco.Core/Dynamics/DynamicDocumentList.cs @@ -53,6 +53,9 @@ namespace Umbraco.Core.Dynamics } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { + + //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods and processing this stuff! + var name = binder.Name; if (name == "Where") { @@ -353,7 +356,6 @@ namespace Umbraco.Core.Dynamics var methodTypesToFind = new[] { - typeof(IEnumerable), typeof(IEnumerable), typeof(DynamicDocumentList) }; diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index 1939ace694..fdfa8e35f7 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -13,15 +13,21 @@ namespace Umbraco.Core.Dynamics { - - private static List GetAllExtensionMethods(Type thisType, string name, int argumentCount, bool argsContainsThis) + /// + /// Returns all extension methods found matching the definition + /// + /// + /// + /// + /// + /// + /// + /// NOTE: This will be an intensive method to call!! Results should be cached! + /// + private static IEnumerable GetAllExtensionMethods(Type thisType, string name, int argumentCount, bool argsContainsThis) { //only scan assemblies we know to contain extension methods (user assemblies) - //var assembliesToScan = TypeFinder.GetAssembliesWithKnownExclusions(); - var assembliesToScan = new List() - { - Assembly.Load("Umbraco.Tests") - }; + var assembliesToScan = TypeFinder.GetAssembliesWithKnownExclusions(); //get extension methods from runtime var candidates = ( @@ -36,11 +42,8 @@ namespace Umbraco.Core.Dynamics select method ); - //search an explicit type (e.g. Enumerable, where most of the Linq methods are defined) - //if (explicitTypeToSearch != null) - //{ - candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)); - //} + //add the extension methods defined in IEnumerable + candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)); //filter by name var methodsByName = candidates.Where(m => m.Name == name); @@ -57,7 +60,7 @@ namespace Umbraco.Core.Dynamics method.t != null && MethodArgZeroHasCorrectTargetType(method.m, method.t, thisType) select method); - return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToList(); + return methodsWhereArgZeroIsTargetType.Select(mt => mt.m); } private static bool MethodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type thisType) { @@ -127,30 +130,28 @@ namespace Umbraco.Core.Dynamics genericType = thisType.GetGenericArguments()[0]; } - var methods = GetAllExtensionMethods(thisType, name, args.Length, argsContainsThis); + var methods = GetAllExtensionMethods(thisType, name, args.Length, argsContainsThis) + .ToArray(); - if (methods.Count == 0) + if (!methods.Any()) { return null; } MethodInfo methodToExecute = null; - if (methods.Count > 1) + if (methods.Count() > 1) { //Given the args, lets get the types and compare the type sequence to try and find the correct overload var argTypes = args.ToList().ConvertAll(o => { - Expression oe = (o as Expression); + var oe = (o as Expression); if (oe != null) { return oe.Type.FullName; } return o.GetType().FullName; }); - var methodsWithArgTypes = methods.ConvertAll(method => new { method = method, types = method.GetParameters().ToList().ConvertAll(pi => pi.ParameterType.FullName) }); - var firstMatchingOverload = methodsWithArgTypes.FirstOrDefault(m => - { - return m.types.SequenceEqual(argTypes); - }); + var methodsWithArgTypes = methods.Select(method => new {method, types = method.GetParameters().Select(pi => pi.ParameterType.FullName) }); + var firstMatchingOverload = methodsWithArgTypes.FirstOrDefault(m => m.types.SequenceEqual(argTypes)); if (firstMatchingOverload != null) { methodToExecute = firstMatchingOverload.method; @@ -159,9 +160,9 @@ namespace Umbraco.Core.Dynamics if (methodToExecute == null) { - MethodInfo firstMethod = methods.FirstOrDefault(); + var firstMethod = methods.FirstOrDefault(); // NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods - if (methods.Count > 1) + if (methods.Count() > 1) { var firstGenericMethod = methods.FirstOrDefault(x => x.IsGenericMethodDefinition); if (firstGenericMethod != null) diff --git a/src/Umbraco.Tests/DynamicDocumentTests.cs b/src/Umbraco.Tests/DynamicDocumentTests.cs index c7b30229a6..120e8cd992 100644 --- a/src/Umbraco.Tests/DynamicDocumentTests.cs +++ b/src/Umbraco.Tests/DynamicDocumentTests.cs @@ -6,6 +6,7 @@ using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Dynamics; +using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -54,7 +55,21 @@ namespace Umbraco.Tests } [Test] - public void Run_Custom_Extension_Method() + public void Custom_Extension_Methods() + { + var dynamicNode = GetDynamicNode(1173); + var asDynamic = dynamicNode.AsDynamic(); + + Assert.AreEqual("Hello world", asDynamic.DynamicDocumentNoParameters()); + Assert.AreEqual("Hello world!", asDynamic.DynamicDocumentCustomString("Hello world!")); + Assert.AreEqual("Hello world!" + 123 + false, asDynamic.DynamicDocumentMultiParam("Hello world!", 123, false)); + Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentListMultiParam("Hello world!", 123, false)); + Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentEnumerableMultiParam("Hello world!", 123, false)); + } + + + [Test] + public void Custom_Extension_Method_With_Params() { var dynamicNode = GetDynamicNode(1173); var asDynamic = dynamicNode.AsDynamic(); @@ -321,10 +336,30 @@ namespace Umbraco.Tests public static class DynamicDocumentCustomExtensionMethods { - public static string ReturnHelloWorld(this DynamicDocument doc) + public static string DynamicDocumentNoParameters(this DynamicDocument doc) { return "Hello world"; } + + public static string DynamicDocumentCustomString(this DynamicDocument doc, string custom) + { + return custom; + } + + public static string DynamicDocumentMultiParam(this DynamicDocument doc, string custom, int i, bool b) + { + return custom + i + b; + } + + public static string DynamicDocumentListMultiParam(this DynamicDocumentList doc, string custom, int i, bool b) + { + return custom + i + b; + } + + public static string DynamicDocumentEnumerableMultiParam(this IEnumerable doc, string custom, int i, bool b) + { + return custom + i + b; + } } } \ No newline at end of file