diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs
index 95ed389a7a..94394abb3e 100644
--- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs
+++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs
@@ -5,6 +5,7 @@ using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
+using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Dynamics
@@ -34,38 +35,42 @@ namespace Umbraco.Core.Dynamics
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)
+ ///
+ /// 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(thisObject, binder, args, null);
+ 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(T thisObject,
+ ///
+ /// 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)
@@ -76,9 +81,9 @@ namespace Umbraco.Core.Dynamics
{
//Property?
result = typeof(T).InvokeMember(binder.Name,
- System.Reflection.BindingFlags.Instance |
- System.Reflection.BindingFlags.Public |
- System.Reflection.BindingFlags.GetProperty,
+ BindingFlags.Instance |
+ BindingFlags.Public |
+ BindingFlags.GetProperty,
null,
thisObject,
args);
@@ -90,10 +95,10 @@ namespace Umbraco.Core.Dynamics
{
//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,
+ BindingFlags.Instance |
+ BindingFlags.Public |
+ BindingFlags.Static |
+ BindingFlags.InvokeMethod,
null,
thisObject,
args);
@@ -105,7 +110,7 @@ namespace Umbraco.Core.Dynamics
{
try
{
- result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes);
+ result = FindAndExecuteExtensionMethod(runtimeCache, thisObject, args, binder.Name, findExtensionMethodsOnTypes);
return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod));
}
catch (TargetInvocationException ext)
@@ -138,16 +143,19 @@ namespace Umbraco.Core.Dynamics
}
}
- ///
- /// 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,
+ ///
+ /// 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)
@@ -158,7 +166,7 @@ namespace Umbraco.Core.Dynamics
MethodInfo toExecute = null;
foreach (var t in findMethodsOnTypes)
{
- toExecute = ExtensionMethodFinder.FindExtensionMethod(t, args, name, false);
+ toExecute = ExtensionMethodFinder.FindExtensionMethod(runtimeCache, t, args, name, false);
if (toExecute != null)
break;
}
diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs
index 7a4d714fbe..5fd97fc778 100644
--- a/src/Umbraco.Core/Dynamics/DynamicXml.cs
+++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs
@@ -10,6 +10,7 @@ using System.Collections;
using System.IO;
using System.Web;
using Umbraco.Core;
+using Umbraco.Core.Cache;
namespace Umbraco.Core.Dynamics
{
@@ -162,8 +163,10 @@ namespace Umbraco.Core.Dynamics
return true; //anyway
}
+ var runtimeCache = ApplicationContext.Current != null ? ApplicationContext.Current.ApplicationCache.RuntimeCache : new NullCacheProvider();
+
//ok, now lets try to match by member, property, extensino method
- var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[]
+ var attempt = DynamicInstanceHelper.TryInvokeMember(runtimeCache, this, binder, args, new[]
{
typeof (IEnumerable),
typeof (IEnumerable),
diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs
index cfc8a92ddf..fbfa933e83 100644
--- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs
+++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs
@@ -1,11 +1,11 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Web.Compilation;
using System.Runtime.CompilerServices;
-using System.Collections;
using System.Linq.Expressions;
+using Umbraco.Core.Cache;
namespace Umbraco.Core.Dynamics
{
@@ -14,148 +14,89 @@ namespace Umbraco.Core.Dynamics
///
internal static class ExtensionMethodFinder
{
- private static readonly MethodInfo[] AllExtensionMethods;
+ ///
+ /// The static cache for extension methods found that match the criteria that we are looking for
+ ///
+ private static readonly ConcurrentDictionary, MethodInfo[]> MethodCache = new ConcurrentDictionary, MethodInfo[]>();
- static ExtensionMethodFinder()
- {
- AllExtensionMethods = TypeFinder.GetAssembliesWithKnownExclusions()
+ ///
+ /// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!!
+ ///
+ ///
+ ///
+ /// We cache this as a sliding 5 minute exiration, in unit tests there's over 1100 methods found, surely that will eat up a bit of memory so we want
+ /// to make sure we give it back.
+ ///
+ private static IEnumerable GetAllExtensionMethodsInAppDomain(IRuntimeCacheProvider runtimeCacheProvider)
+ {
+ if (runtimeCacheProvider == null) throw new ArgumentNullException("runtimeCacheProvider");
+
+ return runtimeCacheProvider.GetCacheItem(typeof (ExtensionMethodFinder).Name, () => TypeFinder.GetAssembliesWithKnownExclusions()
// assemblies that contain extension methods
- .Where(a => a.IsDefined(typeof(ExtensionAttribute), false))
+ .Where(a => a.IsDefined(typeof (ExtensionAttribute), false))
// types that contain extension methods
.SelectMany(a => a.GetTypes()
- .Where(t => t.IsDefined(typeof(ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false))
+ .Where(t => t.IsDefined(typeof (ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false))
// actual extension methods
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)
- .Where(m => m.IsDefined(typeof(ExtensionAttribute), false)))
+ .Where(m => m.IsDefined(typeof (ExtensionAttribute), false)))
// and also IEnumerable extension methods - because the assembly is excluded
- .Concat(typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public))
- .ToArray();
- }
+ .Concat(typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public))
+ //If we don't do this then we'll be scanning all assemblies each time!
+ .ToArray(),
+
+ //only cache for 5 minutes
+ timeout: TimeSpan.FromMinutes(5),
- // ORIGINAL CODE IS NOT COMPLETE, DOES NOT HANDLE GENERICS, ETC...
-
- // so this is an attempt at fixing things, but it's not done yet
- // and do we really want to do this? extension methods are not supported on dynamics, period
- // we should use strongly typed content instead of dynamics.
-
- /*
-
- // get all extension methods for type thisType, with name name,
- // accepting argsCount arguments (not counting the instance of thisType).
- private static IEnumerable GetExtensionMethods(Type thisType, string name, int argsCount)
- {
- var key = string.Format("{0}.{1}::{2}", thisType.FullName, name, argsCount);
-
- var types = thisType.GetBaseTypes(true); // either do this OR have MatchFirstParameter handle the stuff... F*XME
-
- var methods = AllExtensionMethods
- .Where(m => m.Name == name)
- .Where(m => m.GetParameters().Length == argsCount)
- .Where(m => MatchFirstParameter(thisType, m.GetParameters()[0].ParameterType));
-
- // f*xme - is this what we should cache?
- return methods;
+ //each time this is accessed it will be for 5 minutes longer
+ isSliding:true);
}
- // find out whether the first parameter is a match for thisType
- private static bool MatchFirstParameter(Type thisType, Type firstParameterType)
- {
- return MethodArgZeroHasCorrectTargetType(null, firstParameterType, thisType);
- }
+ ///
+ /// Returns all extension methods found matching the definition
+ ///
+ ///
+ /// The runtime cache is used to temporarily cache all extension methods found in the app domain so that
+ /// while we search for individual extension methods, the process will be reasonably 'quick'. We then statically
+ /// cache the MethodInfo's that we are looking for and then the runtime cache will expire and give back all that memory.
+ ///
+ ///
+ ///
+ ///
+ /// The arguments EXCLUDING the 'this' argument in an extension method
+ ///
+ ///
+ ///
+ /// NOTE: This will be an intensive method to call! Results will be cached based on the key (args) of this method
+ ///
+ internal static IEnumerable GetAllExtensionMethods(IRuntimeCacheProvider runtimeCache, Type thisType, string name, int argumentCount)
+ {
+ var key = new Tuple(thisType, name, argumentCount);
- // get the single extension method for type thisType, with name name,
- // that accepts the arguments in args (which does not contain the instance of thisType).
- public static MethodInfo GetExtensionMethod(Type thisType, string name, object[] args)
- {
- MethodInfo result = null;
- foreach (var method in GetExtensionMethods(thisType, name, args.Length).Where(m => MatchParameters(m, args)))
- {
- if (result == null)
- result = method;
- else
- throw new AmbiguousMatchException("More than one matching extension method was found.");
- }
- return result;
- }
+ return MethodCache.GetOrAdd(key, tuple =>
+ {
+ var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache);
- // find out whether the method can accept the arguments
- private static bool MatchParameters(MethodInfo method, IList