diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index c680dce419..feb530ce99 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -28,7 +28,7 @@ - + diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs index 94394abb3e..59f149ef09 100644 --- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs +++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs @@ -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 diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index fbfa933e83..0fe21da013 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -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 which is IEnumerable) + var filtr3 = filtr2.Select(x => + { + var t = x.GetParameters()[0].ParameterType; // exists because of +1 above + var bindings = new Dictionary>(); + if (thisType.MatchType(t, bindings) == false) return null; - //find the right overload that can take genericParameterType - //which will be either DynamicNodeList or List 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 methods, Type genericType, IEnumerable args) diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 98d5125f44..9c015adad4 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -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 allStuff, Type type) + { + var at1 = new List(); + 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> 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(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 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>()); + } + + internal static bool MatchType(this Type inst, Type type, IDictionary> 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(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 + } } \ No newline at end of file diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index a9c1aede4a..53441d7ea1 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -303,5 +303,15 @@ namespace Umbraco.Core return query; } + + /// + /// Removes the port from the uri. + /// + /// The uri. + /// The same uri, without its port. + public static Uri WithoutPort(this Uri uri) + { + return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs index 37b0a63866..b9f16465a6 100644 --- a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs +++ b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs @@ -14,6 +14,147 @@ namespace Umbraco.Tests.DynamicsAndReflection [TestFixture] public class ExtensionMethodFinderTests { + #region Tests Elements + + public class TestClass { } + public class TestClass : TestClass { } + public class TestClassOfInt : TestClass { } + public class TestClassOfString : TestClass { } + + public void TestMethod1(int value) { } + public void TestMethod2(T value) { } + public void TestMethod3(T value1, T value2) { } + public void TestMethod4(T1 value1, T2 value2) { } + public void TestMethod5(List value) { } + public void TestMethod6(int value) { } + public void TestMethod6(string value) { } + public void TestMethod7(IList value) { } + + public interface ITestDict : IDictionary { } + + #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(); + + var method = FindExtensionMethod(typeof(IEnumerable), new object[] { 1 }, "SimpleEnumerableGenericMethod", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, 1 }); + + method = FindExtensionMethod(typeof(IEnumerable), new object[] { "x" }, "SimpleEnumerableGenericMethod", false); + Assert.IsNull(method); + } + + [Test] + public void Find_OverloadedGenericEnumerable() + { + var class1 = Enumerable.Empty(); + + var method = FindExtensionMethod(typeof(IEnumerable), new object[] { 1 }, "SimpleOverloadEnumerableGenericMethod", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, 1 }); + + method = FindExtensionMethod(typeof(IEnumerable), new object[] { "x" }, "SimpleOverloadEnumerableGenericMethod", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, "x" }); + } + + [Test] + public void Find_InheritedType() + { + var genericTestClass = new 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(); + + var method = FindExtensionMethod(c.GetType(), new object[] { }, "GenericMethod", false); + Assert.IsNotNull(method); + } + + [Test] + public void GetMethodVsGetMethods() + { + Assert.Throws(() => + { + 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); + 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 + 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>(); + Assert.IsTrue(typeof(int).MatchType(typeof(int), bindings)); + Assert.AreEqual(0, bindings.Count); + + bindings = new Dictionary>(); + Assert.IsFalse(typeof(int).MatchType(typeof(string), bindings)); + Assert.AreEqual(0, bindings.Count); + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(List).MatchType(typeof(System.Collections.IEnumerable), bindings)); + Assert.AreEqual(0, bindings.Count); + + var m = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod7"); + var t1 = m.GetParameters()[0].ParameterType; // List + var t2 = m.GetParameters()[0].ParameterType.GetGenericArguments()[0]; // + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(int).MatchType(t2, bindings)); + Assert.AreEqual(1, bindings.Count); + Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(IList).MatchType(t1, bindings)); + Assert.AreEqual(1, bindings.Count); + Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(List).MatchType(typeof(IList), bindings)); + Assert.AreEqual(0, bindings.Count); + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(List).MatchType(t1, bindings)); + Assert.AreEqual(1, bindings.Count); + Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + + bindings = new Dictionary>(); + Assert.IsTrue(typeof(Dictionary).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()); Assert.IsFalse(typeof(int[]).Inherits()); - 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 with List - var a5 = new object[] {new List()}; + var a5 = new object[] { new List() }; 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 value) {} - public void TestMethod3(T value1, T value2) { } - public void TestMethod4(T1 value1, T2 value2) { } - public void TestMethod5(List 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 : 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(); - - method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable), new object[] { 1 }, "SimpleEnumerableGenericMethod", false); - Assert.IsNotNull(method); - method.Invoke(null, new object[] { class1, 1 }); - - method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable), new object[] { "x" }, "SimpleEnumerableGenericMethod", false); - Assert.IsNull(method); - } - - [Test] - public void Find_Overloaded_Generic_Enumerable_Method() - { - MethodInfo method; - var class1 = Enumerable.Empty(); - - method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable), new object[] { 1 }, "SimpleOverloadEnumerableGenericMethod", false); - Assert.IsNotNull(method); - method.Invoke(null, new object[] { class1, 1 }); - - method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable), 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(); - 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(this ExtensionMethodFinderTests.TestClass source) + { } } + + #endregion } diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index a4dcaff25b..40597dc285 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -100,6 +100,7 @@ namespace Umbraco.Tests.Services foreach (var dictionaryItem in item) { + Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId); Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); } } diff --git a/src/Umbraco.Tests/UriExtensionsTests.cs b/src/Umbraco.Tests/UriExtensionsTests.cs index 8e826fc428..1b0ae96621 100644 --- a/src/Umbraco.Tests/UriExtensionsTests.cs +++ b/src/Umbraco.Tests/UriExtensionsTests.cs @@ -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()); + } } } diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 01a18ac39d..9214010aef 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -101,7 +101,7 @@ The content has been re-published. Current Property Current type - The document type cannot be changed, as there are no alternatives valid for this location. + 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. Document Type Changed Map Properties Map to Property diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 2a48541a6b..24216900df 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -103,7 +103,7 @@ The content has been re-published. Current Property Current type - The document type cannot be changed, as there are no alternatives valid for this location. + 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. Document Type Changed Map Properties Map to Property diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx index bc93d97487..d119637384 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx @@ -51,7 +51,7 @@ -
+

<%=umbraco.ui.Text("changeDocType", "docTypeCannotBeChanged") %>

@@ -91,15 +91,14 @@ -

<%=umbraco.ui.Text("changeDocType", "docTypeChanged") %>

-
-
+

<%=umbraco.ui.Text("changeDocType", "docTypeChanged") %>

+



<%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %> -

+

diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index 38c6dfd2df..ce9d84f31b 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -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 RemoveCurrentDocumentTypeFromAlternatives(IEnumerable documentTypes) + { + return documentTypes + .Where(x => x.Id != _content.ContentType.Id); + } + + private IEnumerable RemoveInvalidByParentDocumentTypesFromAlternatives(IEnumerable 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 RemoveInvalidByChildrenDocumentTypesFromAlternatives(IEnumerable 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 diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index ef202b5599..72360d9f22 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -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) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 3a051939a5..6ad092c2b6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -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; } }