From dc40b73fef59d03459a25409fafab3509a211a9b Mon Sep 17 00:00:00 2001
From: AndyButland
Date: Tue, 27 Jan 2015 22:34:57 +0100
Subject: [PATCH 1/9] When changing document type, added filter on populating
list of available document types to include only those types that would
permit the existing children to be created under them
---
.../umbraco/dialogs/ChangeDocType.aspx.cs | 23 +++++++++++++++----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
index 38c6dfd2df..ff7969ed44 100644
--- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
+++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
@@ -68,23 +68,36 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs
var documentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes();
// Remove current one
- documentTypes = documentTypes.Where(x => x.Id != _content.ContentType.Id);
+ 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);
+ 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));
+ documentTypes = documentTypes
+ .Where(x => parentNode.ContentType.AllowedContentTypes
+ .Select(y => y.Id.Value)
+ .Contains(x.Id));
}
+ // Remove any that existing children would not be valid for
+ var docTypeIdsOfChildren = _content.Children()
+ .Select(x => x.ContentType.Id)
+ .Distinct()
+ .ToList();
+ documentTypes = documentTypes
+ .Where(x => x.AllowedContentTypes
+ .Select(y => y.Id.Value)
+ .ContainsAll(docTypeIdsOfChildren));
+
// If we have at least one, bind to list and return true
if (documentTypes.Any())
{
From 1289d3255c4723499a394605bf044c0ec58461ea Mon Sep 17 00:00:00 2001
From: AndyButland
Date: Tue, 27 Jan 2015 22:35:17 +0100
Subject: [PATCH 2/9] Presentation improvements for confirmation page of change
document type
---
src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx
index bc93d97487..742816fbd9 100644
--- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx
+++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx
@@ -91,15 +91,14 @@
- <%=umbraco.ui.Text("changeDocType", "docTypeChanged") %>
-
-
+
From 24915163ffe3ac02d1a97ac5dbcbc2f149827b42 Mon Sep 17 00:00:00 2001
From: AndyButland
Date: Wed, 28 Jan 2015 09:10:22 +0100
Subject: [PATCH 3/9] Improved validation label for why a change of document
type isn't available for a given node
---
src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +-
src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index e43af2752f..e58bc75d70 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -100,7 +100,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 0d700fa50f..41d7f43a24 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -102,7 +102,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
From 199bf0ff6ea9a29768b05ce6707fc44b056e4bd1 Mon Sep 17 00:00:00 2001
From: AndyButland
Date: Wed, 28 Jan 2015 09:22:33 +0100
Subject: [PATCH 4/9] Minor code tidy of change content type
---
.../umbraco/dialogs/ChangeDocType.aspx | 2 +-
.../umbraco/dialogs/ChangeDocType.aspx.cs | 73 +++++++++++--------
2 files changed, 43 insertions(+), 32 deletions(-)
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx
index 742816fbd9..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") %>
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
index ff7969ed44..ce9d84f31b 100644
--- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
+++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs
@@ -64,40 +64,14 @@ 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 invalid ones from list of potential alternatives
+ documentTypes = RemoveCurrentDocumentTypeFromAlternatives(documentTypes);
+ documentTypes = RemoveInvalidByParentDocumentTypesFromAlternatives(documentTypes);
+ documentTypes = RemoveInvalidByChildrenDocumentTypesFromAlternatives(documentTypes);
- // 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 any that existing children would not be valid for
- var docTypeIdsOfChildren = _content.Children()
- .Select(x => x.ContentType.Id)
- .Distinct()
- .ToList();
- documentTypes = documentTypes
- .Where(x => x.AllowedContentTypes
- .Select(y => y.Id.Value)
- .ContainsAll(docTypeIdsOfChildren));
-
// If we have at least one, bind to list and return true
if (documentTypes.Any())
{
@@ -111,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
From 08a2c0d0823e92eee1fbfbe9241deab458d6396a Mon Sep 17 00:00:00 2001
From: Stephan
Date: Fri, 30 Jan 2015 14:44:47 +0100
Subject: [PATCH 5/9] U4-6184 - pipeline falls back from example.com:666 to
example.com
---
src/Umbraco.Core/UriExtensions.cs | 10 ++++++++++
src/Umbraco.Tests/UriExtensionsTests.cs | 19 +++++++++++++++++++
src/Umbraco.Web/Routing/DomainHelper.cs | 6 ++++++
3 files changed, 35 insertions(+)
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/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/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs
index 1efe0eed0d..9e81b8a201 100644
--- a/src/Umbraco.Web/Routing/DomainHelper.cs
+++ b/src/Umbraco.Web/Routing/DomainHelper.cs
@@ -159,6 +159,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)
{
From a4d4cbd99f21ebffa81378df90476e7ddfa54c9a Mon Sep 17 00:00:00 2001
From: Stephan
Date: Fri, 30 Jan 2015 15:02:21 +0100
Subject: [PATCH 6/9] U4-6034 - issue when renaming tabs
---
.../umbraco/controls/ContentTypeControlNew.ascx.cs | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
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;
}
}
From ba54f96efd9b5b38e7f33bec5b5f22967521e755 Mon Sep 17 00:00:00 2001
From: Shannon
Date: Tue, 3 Feb 2015 13:16:30 +1100
Subject: [PATCH 7/9] updates nuspec
---
build/NuSpecs/UmbracoCms.Core.nuspec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 @@
-
+
From 40cc4b90fc1ba002fb5daac43aab0000cf6ca041 Mon Sep 17 00:00:00 2001
From: Shannon
Date: Tue, 3 Feb 2015 14:41:43 +1100
Subject: [PATCH 8/9] updates a test
---
src/Umbraco.Tests/Services/LocalizationServiceTests.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs
index 0d78f56939..2bd7b5fcf7 100644
--- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs
+++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs
@@ -99,6 +99,7 @@ namespace Umbraco.Tests.Services
foreach (var dictionaryItem in item)
{
+ Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId);
Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey));
}
}
From 2cdc89297f57664d9598823b1002c08edfbdd91a Mon Sep 17 00:00:00 2001
From: Stephan
Date: Tue, 3 Feb 2015 19:33:39 +0100
Subject: [PATCH 9/9] U4-6210 - fix failing tests, improve extension methods
support on dynamics
---
.../Dynamics/DynamicInstanceHelper.cs | 7 +
.../Dynamics/ExtensionMethodFinder.cs | 95 +----
src/Umbraco.Core/TypeExtensions.cs | 102 +++++-
.../ExtensionMethodFinderTests.cs | 341 ++++++++++++------
4 files changed, 360 insertions(+), 185 deletions(-)
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.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
}