Merge branch 'dev-v7' into 7.3.0
This commit is contained in:
@@ -174,6 +174,13 @@ namespace Umbraco.Core.Dynamics
|
||||
if (toExecute != null)
|
||||
{
|
||||
var genericArgs = (new[] { (object)thisObject }).Concat(args);
|
||||
|
||||
// else we'd get an exception w/ message "Late bound operations cannot
|
||||
// be performed on types or methods for which ContainsGenericParameters is true."
|
||||
// because MakeGenericMethod must be used to obtain an actual method that can run
|
||||
if (toExecute.ContainsGenericParameters)
|
||||
throw new InvalidOperationException("Method contains generic parameters, something's wrong.");
|
||||
|
||||
result = toExecute.Invoke(null, genericArgs.ToArray());
|
||||
}
|
||||
else
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq.Expressions;
|
||||
using System.Web.Services.Description;
|
||||
using Umbraco.Core.Cache;
|
||||
|
||||
namespace Umbraco.Core.Dynamics
|
||||
@@ -77,87 +78,31 @@ namespace Umbraco.Core.Dynamics
|
||||
{
|
||||
var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache);
|
||||
|
||||
// filter by name
|
||||
var filtr1 = candidates.Where(m => m.Name == name);
|
||||
|
||||
//filter by name
|
||||
var methodsByName = candidates.Where(m => m.Name == name);
|
||||
// filter by args count
|
||||
// ensure we add + 1 to the arg count because the 'this' arg is not included in the count above!
|
||||
var filtr2 = filtr1.Where(m => m.GetParameters().Length == argumentCount + 1);
|
||||
|
||||
//ensure we add + 1 to the arg count because the 'this' arg is not included in the count above!
|
||||
var isGenericAndRightParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + 1);
|
||||
// filter by first parameter type (target of the extension method)
|
||||
// ie find the right overload that can take genericParameterType
|
||||
// (which will be either DynamicNodeList or List<DynamicNode> which is IEnumerable)
|
||||
var filtr3 = filtr2.Select(x =>
|
||||
{
|
||||
var t = x.GetParameters()[0].ParameterType; // exists because of +1 above
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
if (thisType.MatchType(t, bindings) == false) return null;
|
||||
|
||||
//find the right overload that can take genericParameterType
|
||||
//which will be either DynamicNodeList or List<DynamicNode> which is IEnumerable`
|
||||
// create the generic method if necessary
|
||||
if (x.ContainsGenericParameters == false) return x;
|
||||
var targs = t.GetGenericArguments().Select(y => bindings[y.Name].First()).ToArray();
|
||||
return x.MakeGenericMethod(targs);
|
||||
}).Where(x => x != null);
|
||||
|
||||
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, thisType)
|
||||
select method);
|
||||
|
||||
return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToArray();
|
||||
return filtr3.ToArray();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static bool MethodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type thisType)
|
||||
{
|
||||
//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?
|
||||
MethodArgZeroHasCorrectTargetTypeTypeMatchesExactly(method, firstArgumentType, thisType) ||
|
||||
|
||||
// or on any of my interfaces?
|
||||
MethodArgZeroHasCorrectTargetTypeAnInterfaceMatches(method, firstArgumentType, thisType) ||
|
||||
|
||||
// or on any of my base types?
|
||||
MethodArgZeroHasCorrectTargetTypeIsASubclassOf(method, firstArgumentType, thisType) ||
|
||||
|
||||
//share a common interface (e.g. IEnumerable)
|
||||
MethodArgZeroHasCorrectTargetTypeShareACommonInterface(method, firstArgumentType, thisType);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static bool MethodArgZeroHasCorrectTargetTypeShareACommonInterface(MethodInfo method, Type firstArgumentType, Type thisType)
|
||||
{
|
||||
var interfaces = firstArgumentType.GetInterfaces();
|
||||
if (interfaces.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = interfaces.All(i => thisType.GetInterfaces().Contains(i));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool MethodArgZeroHasCorrectTargetTypeIsASubclassOf(MethodInfo method, Type firstArgumentType, Type thisType)
|
||||
{
|
||||
var result = thisType.IsSubclassOf(firstArgumentType);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool MethodArgZeroHasCorrectTargetTypeAnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type thisType)
|
||||
{
|
||||
var result = thisType.GetInterfaces().Contains(firstArgumentType);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool MethodArgZeroHasCorrectTargetTypeTypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type thisType)
|
||||
{
|
||||
var result = (thisType == firstArgumentType);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Type FirstParameterType(MethodInfo m)
|
||||
{
|
||||
var p = m.GetParameters();
|
||||
if (p.Any())
|
||||
{
|
||||
return p.First().ParameterType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MethodInfo DetermineMethodFromParams(IEnumerable<MethodInfo> methods, Type genericType, IEnumerable<object> args)
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
throw new ArgumentNullException("genericType");
|
||||
}
|
||||
if (!genericType.IsGenericType)
|
||||
if (genericType.IsGenericType == false)
|
||||
{
|
||||
throw new ArgumentException("genericType must be a generic type");
|
||||
}
|
||||
@@ -258,7 +258,6 @@ namespace Umbraco.Core
|
||||
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
@@ -388,5 +387,102 @@ namespace Umbraco.Core
|
||||
{
|
||||
return string.Concat(type.FullName, ", ", type.Assembly.GetName().Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Match Type
|
||||
|
||||
private static void ReduceGenericParameterCandidateTypes(ICollection<Type> allStuff, Type type)
|
||||
{
|
||||
var at1 = new List<Type>();
|
||||
var t = type;
|
||||
while (t != null)
|
||||
{
|
||||
at1.Add(t);
|
||||
t = t.BaseType;
|
||||
}
|
||||
var r = allStuff.Where(x => x.IsClass && at1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
var ai1 = type.GetInterfaces();
|
||||
if (type.IsInterface) ai1 = ai1.Union(new[] { type }).ToArray();
|
||||
r = allStuff.Where(x => x.IsInterface && ai1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
}
|
||||
|
||||
private static bool MatchGeneric(Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (inst.IsGenericType == false) return false;
|
||||
|
||||
var instd = inst.GetGenericTypeDefinition();
|
||||
var typed = type.GetGenericTypeDefinition();
|
||||
|
||||
if (instd != typed) return false;
|
||||
|
||||
var insta = inst.GetGenericArguments();
|
||||
var typea = type.GetGenericArguments();
|
||||
|
||||
if (insta.Length != typea.Length) return false;
|
||||
|
||||
// but... there is no ZipWhile, and we have arrays anyway
|
||||
//var x = insta.Zip<Type, Type, bool>(typea, (instax, typeax) => { ... });
|
||||
|
||||
for (var i = 0; i < insta.Length; i++)
|
||||
if (MatchType(insta[i], typea[i], bindings) == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetGenericParameterCandidateTypes(Type type)
|
||||
{
|
||||
yield return type;
|
||||
var t = type.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
yield return t;
|
||||
t = t.BaseType;
|
||||
}
|
||||
foreach (var i in type.GetInterfaces())
|
||||
yield return i;
|
||||
}
|
||||
|
||||
public static bool MatchType(this Type inst, Type type)
|
||||
{
|
||||
return MatchType(inst, type, new Dictionary<string, List<Type>>());
|
||||
}
|
||||
|
||||
internal static bool MatchType(this Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
if (MatchGeneric(inst, type, bindings)) return true;
|
||||
var t = inst.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
if (MatchGeneric(t, type, bindings)) return true;
|
||||
t = t.BaseType;
|
||||
}
|
||||
return inst.GetInterfaces().Any(i => MatchGeneric(i, type, bindings));
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
if (bindings.ContainsKey(type.Name))
|
||||
{
|
||||
ReduceGenericParameterCandidateTypes(bindings[type.Name], inst);
|
||||
return bindings[type.Name].Count > 0;
|
||||
}
|
||||
|
||||
bindings[type.Name] = new List<Type>(GetGenericParameterCandidateTypes(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inst == type) return true;
|
||||
if (type.IsClass && inst.IsClass && inst.IsSubclassOf(type)) return true;
|
||||
if (type.IsInterface && inst.GetInterfaces().Contains(type)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -303,5 +303,15 @@ namespace Umbraco.Core
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the port from the uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri.</param>
|
||||
/// <returns>The same uri, without its port.</returns>
|
||||
public static Uri WithoutPort(this Uri uri)
|
||||
{
|
||||
return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,147 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
[TestFixture]
|
||||
public class ExtensionMethodFinderTests
|
||||
{
|
||||
#region Tests Elements
|
||||
|
||||
public class TestClass { }
|
||||
public class TestClass<T> : TestClass { }
|
||||
public class TestClassOfInt : TestClass<int> { }
|
||||
public class TestClassOfString : TestClass<string> { }
|
||||
|
||||
public void TestMethod1(int value) { }
|
||||
public void TestMethod2<T>(T value) { }
|
||||
public void TestMethod3<T>(T value1, T value2) { }
|
||||
public void TestMethod4<T1, T2>(T1 value1, T2 value2) { }
|
||||
public void TestMethod5<T>(List<T> value) { }
|
||||
public void TestMethod6(int value) { }
|
||||
public void TestMethod6(string value) { }
|
||||
public void TestMethod7<T>(IList<T> value) { }
|
||||
|
||||
public interface ITestDict<T> : IDictionary<T, T> { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
private static readonly IRuntimeCacheProvider NullCache = new NullCacheProvider();
|
||||
|
||||
private static MethodInfo FindExtensionMethod(Type thisType, object[] args, string name, bool argsContainsThis)
|
||||
{
|
||||
return ExtensionMethodFinder.FindExtensionMethod(NullCache, thisType, args, name, argsContainsThis);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tests Set #1
|
||||
|
||||
[Test]
|
||||
public void Find_Non_Overloaded_Method()
|
||||
{
|
||||
var class1 = new TestClass();
|
||||
|
||||
var method = FindExtensionMethod(typeof(TestClass), new object[] { 1 }, "SimpleMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = FindExtensionMethod(typeof(TestClass), new object[] { "x" }, "SimpleMethod", false);
|
||||
Assert.IsNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_SimpleOverloaded()
|
||||
{
|
||||
var class1 = new TestClass();
|
||||
|
||||
var method = FindExtensionMethod(typeof(TestClass), new object[] { 1 }, "SimpleOverloadMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = FindExtensionMethod(typeof(TestClass), new object[] { "x" }, "SimpleOverloadMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_SimpleOverloaded_ArgsContainingThis()
|
||||
{
|
||||
var class1 = new TestClass();
|
||||
|
||||
var method = FindExtensionMethod(typeof(TestClass), new object[] { class1, 1 }, "SimpleOverloadMethod", true);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = FindExtensionMethod(typeof(TestClass), new object[] { class1, "x" }, "SimpleOverloadMethod", true);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_NonOverloadedGenericEnumerable()
|
||||
{
|
||||
var class1 = Enumerable.Empty<TestClass>();
|
||||
|
||||
var method = FindExtensionMethod(typeof(IEnumerable<TestClass>), new object[] { 1 }, "SimpleEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = FindExtensionMethod(typeof(IEnumerable<TestClass>), new object[] { "x" }, "SimpleEnumerableGenericMethod", false);
|
||||
Assert.IsNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_OverloadedGenericEnumerable()
|
||||
{
|
||||
var class1 = Enumerable.Empty<TestClass>();
|
||||
|
||||
var method = FindExtensionMethod(typeof(IEnumerable<TestClass>), new object[] { 1 }, "SimpleOverloadEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = FindExtensionMethod(typeof(IEnumerable<TestClass>), new object[] { "x" }, "SimpleOverloadEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_InheritedType()
|
||||
{
|
||||
var genericTestClass = new TestClass<TestClass>();
|
||||
var nonGenericTestClass = new TestClass();
|
||||
|
||||
// not really testing "generics" here, just inheritance
|
||||
|
||||
var method = FindExtensionMethod(typeof(TestClass), new object[] { genericTestClass }, "GenericParameterMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
|
||||
method = FindExtensionMethod(typeof(TestClass), new object[] { nonGenericTestClass }, "GenericParameterMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_TrueGeneric()
|
||||
{
|
||||
var c = new TestClass<int>();
|
||||
|
||||
var method = FindExtensionMethod(c.GetType(), new object[] { }, "GenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetMethodVsGetMethods()
|
||||
{
|
||||
Assert.Throws<AmbiguousMatchException>(() =>
|
||||
{
|
||||
var m = typeof (ExtensionMethodFinderTests).GetMethod("TestMethod6");
|
||||
});
|
||||
|
||||
var ms = typeof (ExtensionMethodFinderTests).GetMethods().Where(x => x.Name == "TestMethod6");
|
||||
Assert.AreEqual(2, ms.Count());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tests Set #2 - Working with Generics
|
||||
|
||||
// To expand on Jon's answer, the reason this doesn't work is because in regular,
|
||||
// non-dynamic code extension methods work by doing a full search of all the
|
||||
// classes known to the compiler for a static class that has an extension method
|
||||
@@ -28,6 +169,83 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
// schedule risk to be worth it.
|
||||
//
|
||||
// Eric Lippert, http://stackoverflow.com/questions/5311465/extension-method-and-dynamic-object-in-c-sharp
|
||||
//
|
||||
// And so...
|
||||
// Obviously MatchType is broken and incomplete, it does not handle
|
||||
// - ref & out parameters
|
||||
// - array types
|
||||
// - structs
|
||||
// - generics constraints
|
||||
// - generics variance
|
||||
// - ...
|
||||
|
||||
[Test]
|
||||
public void Temp()
|
||||
{
|
||||
var t1 = typeof (IList<int>);
|
||||
var t2 = typeof (IList<>);
|
||||
Assert.IsTrue(t2.IsGenericTypeDefinition);
|
||||
Assert.AreEqual(t2, t1.GetGenericTypeDefinition());
|
||||
var m = typeof (ExtensionMethodFinderTests).GetMethod("TestMethod7");
|
||||
var parms = m.GetParameters();
|
||||
Assert.AreEqual(1, parms.Length);
|
||||
var parm = parms[0];
|
||||
var t3 = parm.ParameterType; // IList<T>
|
||||
Assert.AreEqual(t2, t3.GetGenericTypeDefinition());
|
||||
|
||||
Assert.AreEqual(typeof (int), t1.GetGenericArguments()[0]);
|
||||
Assert.IsFalse(t1.GetGenericArguments()[0].IsGenericParameter);
|
||||
//Assert.AreEqual(???, t2.GetGenericArguments()[0]);
|
||||
Assert.IsTrue(t2.GetGenericArguments()[0].IsGenericParameter);
|
||||
Assert.AreEqual("T", t2.GetGenericArguments()[0].Name);
|
||||
Assert.IsTrue(t3.GetGenericArguments()[0].IsGenericParameter);
|
||||
Assert.AreEqual("T", t3.GetGenericArguments()[0].Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MatchTypesTest()
|
||||
{
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(int).MatchType(typeof(int), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsFalse(typeof(int).MatchType(typeof(string), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(typeof(System.Collections.IEnumerable), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
var m = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod7");
|
||||
var t1 = m.GetParameters()[0].ParameterType; // List<T>
|
||||
var t2 = m.GetParameters()[0].ParameterType.GetGenericArguments()[0]; // <T>
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(int).MatchType(t2, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(IList<int>).MatchType(t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(typeof(IList<int>), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(Dictionary<int, string>).MatchType(typeof(IDictionary<,>), bindings));
|
||||
Assert.AreEqual(2, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["TKey"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(string), bindings["TValue"].FirstOrDefault());
|
||||
}
|
||||
|
||||
[Ignore("This is just testing the below GetMethodForArguments method - Stephen was working on this but it's not used in the core")]
|
||||
[Test]
|
||||
@@ -36,14 +254,14 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
Assert.IsTrue(typeof(int[]).Inherits<int[]>());
|
||||
Assert.IsFalse(typeof(int[]).Inherits<bool[]>());
|
||||
|
||||
var m1 = typeof (ExtensionMethodFinderTests).GetMethod("TestMethod1");
|
||||
|
||||
var a1A = new object[] {1};
|
||||
var m1 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod1");
|
||||
|
||||
var a1A = new object[] { 1 };
|
||||
var m1A = GetMethodForArguments(m1, a1A);
|
||||
Assert.IsNotNull(m1A);
|
||||
m1A.Invoke(this, a1A);
|
||||
|
||||
var a1B = new object[] {"foo"};
|
||||
var a1B = new object[] { "foo" };
|
||||
var m1B = GetMethodForArguments(m1, a1B);
|
||||
Assert.IsNull(m1B);
|
||||
|
||||
@@ -59,12 +277,12 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
|
||||
var m3 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod3");
|
||||
|
||||
var a3A = new object[] {1, 2};
|
||||
var a3A = new object[] { 1, 2 };
|
||||
var m3A = GetMethodForArguments(m3, a3A);
|
||||
Assert.IsNotNull(m3A);
|
||||
m3A.Invoke(this, a3A);
|
||||
|
||||
var a3B = new object[] {1, "foo"};
|
||||
var a3B = new object[] { 1, "foo" };
|
||||
var m3B = GetMethodForArguments(m3, a3B);
|
||||
Assert.IsNull(m3B);
|
||||
|
||||
@@ -81,7 +299,7 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
var m5 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod5");
|
||||
|
||||
// note - currently that fails because we can't match List<T> with List<int32>
|
||||
var a5 = new object[] {new List<int>()};
|
||||
var a5 = new object[] { new List<int>() };
|
||||
var m5A = GetMethodForArguments(m5, a5);
|
||||
Assert.IsNotNull(m5A);
|
||||
|
||||
@@ -91,12 +309,6 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
// SD: NO, lets not make this more complicated than it already is
|
||||
}
|
||||
|
||||
public void TestMethod1(int value) {}
|
||||
public void TestMethod2<T>(T value) {}
|
||||
public void TestMethod3<T>(T value1, T value2) { }
|
||||
public void TestMethod4<T1, T2>(T1 value1, T2 value2) { }
|
||||
public void TestMethod5<T>(List<T> value) { }
|
||||
|
||||
// gets the method that can apply to the arguments
|
||||
// either the method itself, or a generic one
|
||||
// or null if it couldn't match
|
||||
@@ -156,105 +368,16 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
}
|
||||
}
|
||||
if (i != parameters.Length) return null;
|
||||
return genericArguments.Length == 0
|
||||
? method
|
||||
return genericArguments.Length == 0
|
||||
? method
|
||||
: method.MakeGenericMethod(genericArgumentTypes);
|
||||
}
|
||||
|
||||
public class TestClass
|
||||
{}
|
||||
|
||||
public class TestClass<T> : TestClass { }
|
||||
|
||||
[Test]
|
||||
public void Find_Non_Overloaded_Method()
|
||||
{
|
||||
MethodInfo method;
|
||||
var class1 = new TestClass();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { 1 }, "SimpleMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { "x" }, "SimpleMethod", false);
|
||||
Assert.IsNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_Overloaded_Method()
|
||||
{
|
||||
MethodInfo method;
|
||||
var class1 = new TestClass();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { 1 }, "SimpleOverloadMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { "x" }, "SimpleOverloadMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_Overloaded_Method_With_Args_Containing_This()
|
||||
{
|
||||
MethodInfo method;
|
||||
var class1 = new TestClass();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { class1, 1 }, "SimpleOverloadMethod", true);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { class1, "x" }, "SimpleOverloadMethod", true);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_Non_Overloaded_Generic_Enumerable_Method()
|
||||
{
|
||||
MethodInfo method;
|
||||
var class1 = Enumerable.Empty<TestClass>();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable<TestClass>), new object[] { 1 }, "SimpleEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable<TestClass>), new object[] { "x" }, "SimpleEnumerableGenericMethod", false);
|
||||
Assert.IsNull(method);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_Overloaded_Generic_Enumerable_Method()
|
||||
{
|
||||
MethodInfo method;
|
||||
var class1 = Enumerable.Empty<TestClass>();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable<TestClass>), new object[] { 1 }, "SimpleOverloadEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, 1 });
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable<TestClass>), new object[] { "x" }, "SimpleOverloadEnumerableGenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1, "x" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Find_Method_With_Parameter_Match_With_Generic_Argument()
|
||||
{
|
||||
MethodInfo method;
|
||||
|
||||
var genericTestClass = new TestClass<TestClass>();
|
||||
var nonGenericTestClass = new TestClass();
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { genericTestClass }, "GenericParameterMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClass), new object[] { nonGenericTestClass }, "GenericParameterMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Tests Elements
|
||||
|
||||
static class ExtensionMethodFinderTestsExtensions
|
||||
{
|
||||
public static void SimpleMethod(this ExtensionMethodFinderTests.TestClass source, int value)
|
||||
@@ -278,5 +401,9 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
public static void GenericParameterMethod(this ExtensionMethodFinderTests.TestClass source, ExtensionMethodFinderTests.TestClass value)
|
||||
{ }
|
||||
|
||||
public static void GenericMethod<T>(this ExtensionMethodFinderTests.TestClass<T> source)
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
foreach (var dictionaryItem in item)
|
||||
{
|
||||
Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,5 +181,24 @@ namespace Umbraco.Tests
|
||||
var output = source.MakeAbsolute(absolute);
|
||||
Assert.AreEqual(expected, output.ToString());
|
||||
}
|
||||
|
||||
[TestCase("http://www.domain.com/path/to/page", "http://www.domain.com/path/to/page")]
|
||||
[TestCase("http://www.domain.com/path/to/page/", "http://www.domain.com/path/to/page/")]
|
||||
[TestCase("http://www.domain.com", "http://www.domain.com/")]
|
||||
[TestCase("http://www.domain.com/", "http://www.domain.com/")]
|
||||
[TestCase("http://www.domain.com/path/to?q=3#yop", "http://www.domain.com/path/to?q=3#yop")]
|
||||
[TestCase("http://www.domain.com/path/to/?q=3#yop", "http://www.domain.com/path/to/?q=3#yop")]
|
||||
[TestCase("http://www.domain.com:666/path/to/page", "http://www.domain.com/path/to/page")]
|
||||
[TestCase("http://www.domain.com:666/path/to/page/", "http://www.domain.com/path/to/page/")]
|
||||
[TestCase("http://www.domain.com:666", "http://www.domain.com/")]
|
||||
[TestCase("http://www.domain.com:666/", "http://www.domain.com/")]
|
||||
[TestCase("http://www.domain.com:666/path/to?q=3#yop", "http://www.domain.com/path/to?q=3#yop")]
|
||||
[TestCase("http://www.domain.com:666/path/to/?q=3#yop", "http://www.domain.com/path/to/?q=3#yop")]
|
||||
public void WithoutPort(string input, string expected)
|
||||
{
|
||||
var source = new Uri(input);
|
||||
var output = source.WithoutPort();
|
||||
Assert.AreEqual(expected, output.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<key alias="contentRepublished">The content has been re-published.</key>
|
||||
<key alias="currentProperty">Current Property</key>
|
||||
<key alias="currentType">Current type</key>
|
||||
<key alias="docTypeCannotBeChanged">The document type cannot be changed, as there are no alternatives valid for this location.</key>
|
||||
<key alias="docTypeCannotBeChanged">The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it.</key>
|
||||
<key alias="docTypeChanged">Document Type Changed</key>
|
||||
<key alias="mapProperties">Map Properties</key>
|
||||
<key alias="mapToProperty">Map to Property</key>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<key alias="contentRepublished">The content has been re-published.</key>
|
||||
<key alias="currentProperty">Current Property</key>
|
||||
<key alias="currentType">Current type</key>
|
||||
<key alias="docTypeCannotBeChanged">The document type cannot be changed, as there are no alternatives valid for this location.</key>
|
||||
<key alias="docTypeCannotBeChanged">The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it.</key>
|
||||
<key alias="docTypeChanged">Document Type Changed</key>
|
||||
<key alias="mapProperties">Map Properties</key>
|
||||
<key alias="mapToProperty">Map to Property</key>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</cc1:PropertyPanel>
|
||||
|
||||
<asp:PlaceHolder ID="NotAvailablePlaceholder" runat="server" Visible="false">
|
||||
<div class="propertyItem notice" style="padding: 10px">
|
||||
<div class="propertyItem notice" style="padding-top: 10px">
|
||||
<p><%=umbraco.ui.Text("changeDocType", "docTypeCannotBeChanged") %></p>
|
||||
</div>
|
||||
</asp:PlaceHolder>
|
||||
@@ -91,15 +91,14 @@
|
||||
</cc1:Pane>
|
||||
|
||||
<asp:PlaceHolder ID="SuccessPlaceholder" runat="server" Visible="false">
|
||||
<h2 class="propertypaneTitel"><%=umbraco.ui.Text("changeDocType", "docTypeChanged") %></h2>
|
||||
<br />
|
||||
<div class="success" style="padding: 10px">
|
||||
<p><%=umbraco.ui.Text("changeDocType", "docTypeChanged") %></p>
|
||||
<p>
|
||||
<asp:Literal ID="SuccessMessage" runat="server" />
|
||||
<asp:Literal ID="PropertiesMappedMessage" runat="server" />
|
||||
<asp:Literal ID="ContentPublishedMessage" runat="server" />
|
||||
<br /><br />
|
||||
<a href="#" style="color: blue" onclick="UmbClientMgr.closeModalWindow()"><%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %></a>
|
||||
</div>
|
||||
</p>
|
||||
</asp:PlaceHolder>
|
||||
|
||||
<asp:PlaceHolder ID="ValidationPlaceholder" runat="server" Visible="false">
|
||||
|
||||
@@ -64,26 +64,13 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs
|
||||
|
||||
private bool PopulateListOfValidAlternateDocumentTypes()
|
||||
{
|
||||
// Get all content types
|
||||
// Start with all content types
|
||||
var documentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes();
|
||||
|
||||
// Remove current one
|
||||
documentTypes = documentTypes.Where(x => x.Id != _content.ContentType.Id);
|
||||
|
||||
// Remove any not valid for current location
|
||||
if (_content.ParentId == -1)
|
||||
{
|
||||
// Root content, only include those that have been selected as allowed at root
|
||||
documentTypes = documentTypes.Where(x => x.AllowedAsRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Below root, so only include those allowed as sub-nodes for the parent
|
||||
var parentNode = ApplicationContext.Current.Services.ContentService.GetById(_content.ParentId);
|
||||
documentTypes = documentTypes.Where(x => parentNode.ContentType.AllowedContentTypes
|
||||
.Select(y => y.Id.Value)
|
||||
.Contains(x.Id));
|
||||
}
|
||||
// Remove invalid ones from list of potential alternatives
|
||||
documentTypes = RemoveCurrentDocumentTypeFromAlternatives(documentTypes);
|
||||
documentTypes = RemoveInvalidByParentDocumentTypesFromAlternatives(documentTypes);
|
||||
documentTypes = RemoveInvalidByChildrenDocumentTypesFromAlternatives(documentTypes);
|
||||
|
||||
// If we have at least one, bind to list and return true
|
||||
if (documentTypes.Any())
|
||||
@@ -98,6 +85,43 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<IContentType> RemoveCurrentDocumentTypeFromAlternatives(IEnumerable<IContentType> documentTypes)
|
||||
{
|
||||
return documentTypes
|
||||
.Where(x => x.Id != _content.ContentType.Id);
|
||||
}
|
||||
|
||||
private IEnumerable<IContentType> RemoveInvalidByParentDocumentTypesFromAlternatives(IEnumerable<IContentType> documentTypes)
|
||||
{
|
||||
if (_content.ParentId == -1)
|
||||
{
|
||||
// Root content, only include those that have been selected as allowed at root
|
||||
return documentTypes
|
||||
.Where(x => x.AllowedAsRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Below root, so only include those allowed as sub-nodes for the parent
|
||||
var parentNode = ApplicationContext.Current.Services.ContentService.GetById(_content.ParentId);
|
||||
return documentTypes
|
||||
.Where(x => parentNode.ContentType.AllowedContentTypes
|
||||
.Select(y => y.Id.Value)
|
||||
.Contains(x.Id));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IContentType> RemoveInvalidByChildrenDocumentTypesFromAlternatives(IEnumerable<IContentType> documentTypes)
|
||||
{
|
||||
var docTypeIdsOfChildren = _content.Children()
|
||||
.Select(x => x.ContentType.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
return documentTypes
|
||||
.Where(x => x.AllowedContentTypes
|
||||
.Select(y => y.Id.Value)
|
||||
.ContainsAll(docTypeIdsOfChildren));
|
||||
}
|
||||
|
||||
private void PopulateListOfTemplates()
|
||||
{
|
||||
// Get selected new document type
|
||||
|
||||
@@ -145,6 +145,12 @@ namespace Umbraco.Web.Routing
|
||||
.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(currentWithSlash));
|
||||
if (domainAndUri != null) return domainAndUri;
|
||||
|
||||
// if none matches, try again without the port
|
||||
// ie current is www.example.com:1234/foo/bar, look for domain www.example.com
|
||||
domainAndUri = domainsAndUris
|
||||
.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(currentWithSlash.WithoutPort()));
|
||||
if (domainAndUri != null) return domainAndUri;
|
||||
|
||||
// if none matches, then try to run the filter to pick a domain
|
||||
if (filter != null)
|
||||
{
|
||||
|
||||
@@ -391,16 +391,21 @@ namespace umbraco.controls
|
||||
}
|
||||
}
|
||||
|
||||
var tabs = SaveTabs();
|
||||
var tabs = SaveTabs(); // returns { TabId, TabName, TabSortOrder }
|
||||
foreach (var tab in tabs)
|
||||
{
|
||||
if (_contentType.ContentTypeItem.PropertyGroups.Contains(tab.Item2))
|
||||
var group = _contentType.ContentTypeItem.PropertyGroups.FirstOrDefault(x => x.Id == tab.Item1);
|
||||
if (group == null)
|
||||
{
|
||||
_contentType.ContentTypeItem.PropertyGroups[tab.Item2].SortOrder = tab.Item3;
|
||||
// creating a group
|
||||
group = new PropertyGroup {Id = tab.Item1, Name = tab.Item2, SortOrder = tab.Item3};
|
||||
_contentType.ContentTypeItem.PropertyGroups.Add(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentType.ContentTypeItem.PropertyGroups.Add(new PropertyGroup {Id = tab.Item1, Name = tab.Item2, SortOrder = tab.Item3});
|
||||
// updating an existing group
|
||||
group.Name = tab.Item2;
|
||||
group.SortOrder = tab.Item3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user