From 1e01d8272bf5cc5c4e3ff2cb30edcb5b442fe96d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 13 May 2014 19:26:37 +1000 Subject: [PATCH] Gets segmentation providers in, creates a default one (still needs work), exposes some APIs to get at the information which all happens lazily when required. --- src/Umbraco.Core/Umbraco.Core.csproj | 2 - .../ContentVariationProviderTests.cs | 37 ------ src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs | 1 + .../Routing/ContentSegmentProviderTests.cs | 35 ++++++ .../Routing/ContentSegmentsTests.cs | 47 ++++++++ .../Routing/uQueryGetNodeIdByUrlTests.cs | 4 +- .../TestHelpers/BaseRoutingTest.cs | 1 + src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- src/Umbraco.Web/Routing/RequestSegments.cs | 100 ++++++++++++++++ src/Umbraco.Web/Routing/RoutingContext.cs | 113 +++++++++++------- .../Segments/BrowserCapabilitiesProvider.cs | 41 +++++++ .../Segments/ContentSegmentAttribute.cs} | 6 +- .../Segments/ContentSegmentProvider.cs} | 15 +-- .../ContentSegmentProviderResolver.cs | 40 +++++++ src/Umbraco.Web/Umbraco.Web.csproj | 5 + src/Umbraco.Web/UmbracoContext.cs | 16 +++ src/Umbraco.Web/WebBootManager.cs | 4 + 17 files changed, 372 insertions(+), 98 deletions(-) delete mode 100644 src/Umbraco.Tests/ContentVariations/ContentVariationProviderTests.cs create mode 100644 src/Umbraco.Tests/Routing/ContentSegmentProviderTests.cs create mode 100644 src/Umbraco.Tests/Routing/ContentSegmentsTests.cs create mode 100644 src/Umbraco.Web/Routing/RequestSegments.cs create mode 100644 src/Umbraco.Web/Routing/Segments/BrowserCapabilitiesProvider.cs rename src/{Umbraco.Core/ContentVariations/ContentVariationAttribute.cs => Umbraco.Web/Routing/Segments/ContentSegmentAttribute.cs} (56%) rename src/{Umbraco.Core/ContentVariations/ContentVariationProvider.cs => Umbraco.Web/Routing/Segments/ContentSegmentProvider.cs} (82%) create mode 100644 src/Umbraco.Web/Routing/Segments/ContentSegmentProviderResolver.cs diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 59c7a51710..14797ef36a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -274,8 +274,6 @@ - - diff --git a/src/Umbraco.Tests/ContentVariations/ContentVariationProviderTests.cs b/src/Umbraco.Tests/ContentVariations/ContentVariationProviderTests.cs deleted file mode 100644 index 8f17befb57..0000000000 --- a/src/Umbraco.Tests/ContentVariations/ContentVariationProviderTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using NUnit.Framework; -using Umbraco.Core.ContentVariations; - -namespace Umbraco.Tests.ContentVariations -{ - [TestFixture] - public class ContentVariationProviderTests - { - - [Test] - public void Get_Advertised_Segments() - { - var provider = new MyTestProvider(); - - Assert.AreEqual(3, provider.SegmentsAdvertised.Count()); - } - - [ContentVariation("Test1")] - [ContentVariation("Test2")] - [ContentVariation("Test3")] - [ContentVariation("Test2")] - private class MyTestProvider : ContentVariationProvider - { - public override IEnumerable GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest) - { - return Enumerable.Empty(); - } - } - - } -} diff --git a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs index c9ed53ef2a..db1ce0ad3e 100644 --- a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs @@ -7,6 +7,7 @@ using System.Web.Mvc; using System.Web.Routing; using System.Xml; using NUnit.Framework; +using Umbraco.Web.Routing.Segments; using Umbraco.Web.Security; using umbraco.BusinessLogic; using Umbraco.Core; diff --git a/src/Umbraco.Tests/Routing/ContentSegmentProviderTests.cs b/src/Umbraco.Tests/Routing/ContentSegmentProviderTests.cs new file mode 100644 index 0000000000..4675fd1a8e --- /dev/null +++ b/src/Umbraco.Tests/Routing/ContentSegmentProviderTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using NUnit.Framework; +using Umbraco.Web.Routing.Segments; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class ContentSegmentProviderTests + { + + [Test] + public void Get_Advertised_Segments() + { + var provider = new MyTestProvider(); + + Assert.AreEqual(3, provider.SegmentsAdvertised.Count()); + } + + [ContentSegment("Test1")] + [ContentSegment("Test2")] + [ContentSegment("Test3")] + [ContentSegment("Test2")] + private class MyTestProvider : ContentSegmentProvider + { + public override IDictionary GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest) + { + return new Dictionary(); + } + } + + } +} diff --git a/src/Umbraco.Tests/Routing/ContentSegmentsTests.cs b/src/Umbraco.Tests/Routing/ContentSegmentsTests.cs new file mode 100644 index 0000000000..fa4b5a8d6f --- /dev/null +++ b/src/Umbraco.Tests/Routing/ContentSegmentsTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Moq; +using NUnit.Framework; +using Umbraco.Web.Routing; +using Umbraco.Web.Routing.Segments; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class ContentSegmentsTests + { + + [Test] + public void Get_All_Segments_For_Request() + { + var result = RequestSegments.GetAllSegmentsForRequest(new ContentSegmentProvider[] + { + new MyTestProvider1(), new MyTestProvider2() + }, new Uri("http://localhost/test?blah=1"), new Uri("http://localhost/test/"), Mock.Of()); + + Assert.AreEqual(4, result.Count()); + Assert.AreEqual(true, result["key1"]); + Assert.AreEqual("blah", result["key2"]); + Assert.AreEqual(9876, result["key3"]); + Assert.AreEqual(123, result["key4"]); + } + + private class MyTestProvider1 : ContentSegmentProvider + { + public override IDictionary GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest) + { + return new Dictionary { { "key1", false }, { "key2", "blahdd" }, { "key3", 9876 } }; + } + } + + private class MyTestProvider2 : ContentSegmentProvider + { + public override IDictionary GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest) + { + return new Dictionary {{"key1", true}, {"key2", "blah"}, {"key4", 123}}; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs index e387642ef2..4657ea6b78 100644 --- a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs @@ -1,4 +1,5 @@ -using Moq; +using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; @@ -6,6 +7,7 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.Routing; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.template; +using Umbraco.Web.Routing.Segments; namespace Umbraco.Tests.Routing { diff --git a/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs b/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs index d55a437976..673e00c204 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs @@ -8,6 +8,7 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Routing.Segments; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ad44294f60..bfe782f095 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -169,7 +169,7 @@ - + @@ -317,6 +317,7 @@ + diff --git a/src/Umbraco.Web/Routing/RequestSegments.cs b/src/Umbraco.Web/Routing/RequestSegments.cs new file mode 100644 index 0000000000..4f83bbfef2 --- /dev/null +++ b/src/Umbraco.Web/Routing/RequestSegments.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Umbraco.Web.Routing.Segments; + +namespace Umbraco.Web.Routing +{ + public class RequestSegments + { + private readonly IEnumerable _segmentProviders; + private readonly Lazy> _assignedSegments; + + public RequestSegments(IEnumerable segmentProviders, + Uri originalRequestUrl, + Uri cleanedRequestUrl, + HttpRequestBase httpRequest) + { + _segmentProviders = segmentProviders; + + _assignedSegments = new Lazy>(() => + GetAllSegmentsForRequest( + _segmentProviders, + originalRequestUrl, + cleanedRequestUrl, + httpRequest)); + } + + /// + /// Returns the assigned segments for the current request + /// + public IDictionary AssignedSegments + { + get { return _assignedSegments.Value; } + } + + /// + /// Returns true if any assigned segment value that is a boolean is set to true that matches the specified key + /// + /// + /// + /// + /// Example: RequestIs("Mobile") if a segment key is "Mobile" and it's value is a boolean true. + /// + public bool RequestIs(string segmentKey) + { + return AssignedSegments.Any(x => x.Key == segmentKey && x.Value is bool && (bool)x.Value); + } + + /// + /// Returns true if any assigned segment has a value equal to the one specified + /// + /// + /// + public bool RequestContains(string segmentVal) + { + return AssignedSegments.Any(x => x.Value.ToString() == segmentVal); + } + + /// + /// Returns true if any assigned segment key + value matches the specified parameters + /// + /// + /// + /// + public bool RequestEquals(string key, object val) + { + return AssignedSegments.Any(x => x.Key == key && x.Value == val); + } + + /// + /// Internal so it can be tested + /// + /// + /// + /// + /// + /// + internal static IDictionary GetAllSegmentsForRequest( + IEnumerable segmentProviders, + Uri originalRequestUrl, + Uri cleanedRequestUrl, + HttpRequestBase httpRequest) + { + //get all key/vals, there might be duplicates so we will simply take the last one in + var allKeyVals = segmentProviders.SelectMany(x => + x.GetSegmentsForRequest( + originalRequestUrl, + cleanedRequestUrl, + httpRequest)); + + var d = new Dictionary(); + foreach (var keyVal in allKeyVals) + { + d[keyVal.Key] = keyVal.Value; + } + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/RoutingContext.cs b/src/Umbraco.Web/Routing/RoutingContext.cs index b999e2241a..d496ad4955 100644 --- a/src/Umbraco.Web/Routing/RoutingContext.cs +++ b/src/Umbraco.Web/Routing/RoutingContext.cs @@ -1,77 +1,100 @@ using System; using System.Collections.Generic; +using System.ServiceModel; using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Routing { - - /// - /// Provides context for the routing of a request. - /// + /// + /// Provides context for the routing of a request. + /// public class RoutingContext { - private readonly Lazy _urlProvider; - private readonly Lazy> _publishedContentFinders; - private readonly Lazy _publishedContentLastChanceFinder; + + private readonly Lazy _urlProvider; + private readonly Lazy> _publishedContentFinders; + private readonly Lazy _publishedContentLastChanceFinder; + - /// - /// Initializes a new instance of the class. - /// - /// - /// The document lookups resolver. - /// - /// The nice urls provider. - internal RoutingContext( - UmbracoContext umbracoContext, - IEnumerable contentFinders, - IContentFinder contentLastChanceFinder, + /// + /// Initializes a new instance of the class. + /// + /// + /// The document lookups resolver. + /// + /// The nice urls provider. + internal RoutingContext( + UmbracoContext umbracoContext, + IEnumerable contentFinders, + IContentFinder contentLastChanceFinder, UrlProvider urlProvider) + : this(umbracoContext) { - UmbracoContext = umbracoContext; _publishedContentFinders = new Lazy>(() => contentFinders, false); _publishedContentLastChanceFinder = new Lazy(() => contentLastChanceFinder, false); - _urlProvider = new Lazy(() => urlProvider, false); + _urlProvider = new Lazy(() => urlProvider, false); } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// internal RoutingContext( UmbracoContext umbracoContext, Lazy> contentFinders, Lazy contentLastChanceFinder, Lazy urlProvider) + : this(umbracoContext) { - UmbracoContext = umbracoContext; + _publishedContentFinders = contentFinders; _publishedContentLastChanceFinder = contentLastChanceFinder; _urlProvider = urlProvider; } - /// - /// Gets the Umbraco context. - /// - public UmbracoContext UmbracoContext { get; private set; } - - /// - /// Gets the published content finders. - /// - internal IEnumerable PublishedContentFinders - { - get { return _publishedContentFinders.Value; } + /// + /// Creates the lazy assigned segments logic + /// + /// + private RoutingContext(UmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; } - /// - /// Gets the published content last chance finder. - /// - internal IContentFinder PublishedContentLastChanceFinder - { + + + /// + /// Gets the Umbraco context. + /// + public UmbracoContext UmbracoContext { get; private set; } + + /// + /// Gets the published content finders. + /// + internal IEnumerable PublishedContentFinders + { + get { return _publishedContentFinders.Value; } + } + + /// + /// Gets the published content last chance finder. + /// + internal IContentFinder PublishedContentLastChanceFinder + { get { return _publishedContentLastChanceFinder.Value; } - } + } - /// - /// Gets the urls provider. - /// - public UrlProvider UrlProvider - { - get { return _urlProvider.Value; } - } + /// + /// Gets the urls provider. + /// + public UrlProvider UrlProvider + { + get { return _urlProvider.Value; } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/Segments/BrowserCapabilitiesProvider.cs b/src/Umbraco.Web/Routing/Segments/BrowserCapabilitiesProvider.cs new file mode 100644 index 0000000000..c87bda1ee7 --- /dev/null +++ b/src/Umbraco.Web/Routing/Segments/BrowserCapabilitiesProvider.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web; + +namespace Umbraco.Web.Routing.Segments +{ + /// + /// Assigns segments based on MS's HttpBrowserCapabilities object + /// + internal class BrowserCapabilitiesProvider : ContentSegmentProvider + { + public BrowserCapabilitiesProvider() + { + _browserCapabilityProps = typeof(HttpBrowserCapabilities).GetProperties() + .Where(x => PropNames.Contains(x.Name)) + .ToArray(); + } + + private readonly PropertyInfo[] _browserCapabilityProps; + + private static readonly string[] PropNames = + { + "IsMobileDevice", + "JavaApplets", + "MajorVersion", + "MinorVersion", + "MobileDeviceModel", + "MobileDeviceManufacturer", + "Platform" + }; + + public override IDictionary GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest) + { + return _browserCapabilityProps + .Select(x => new {key = x.Name, val = x.GetValue(httpRequest.Browser, null)}) + .ToDictionary(key => key.key, val => val.val); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ContentVariations/ContentVariationAttribute.cs b/src/Umbraco.Web/Routing/Segments/ContentSegmentAttribute.cs similarity index 56% rename from src/Umbraco.Core/ContentVariations/ContentVariationAttribute.cs rename to src/Umbraco.Web/Routing/Segments/ContentSegmentAttribute.cs index a26e98ed3b..4f85a90524 100644 --- a/src/Umbraco.Core/ContentVariations/ContentVariationAttribute.cs +++ b/src/Umbraco.Web/Routing/Segments/ContentSegmentAttribute.cs @@ -1,13 +1,13 @@ using System; -namespace Umbraco.Core.ContentVariations +namespace Umbraco.Web.Routing.Segments { [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class ContentVariationAttribute : Attribute + public sealed class ContentSegmentAttribute : Attribute { public string SegmentName { get; private set; } - public ContentVariationAttribute(string segmentName) + public ContentSegmentAttribute(string segmentName) { SegmentName = segmentName; } diff --git a/src/Umbraco.Core/ContentVariations/ContentVariationProvider.cs b/src/Umbraco.Web/Routing/Segments/ContentSegmentProvider.cs similarity index 82% rename from src/Umbraco.Core/ContentVariations/ContentVariationProvider.cs rename to src/Umbraco.Web/Routing/Segments/ContentSegmentProvider.cs index c9a5dfea9a..558aa03140 100644 --- a/src/Umbraco.Core/ContentVariations/ContentVariationProvider.cs +++ b/src/Umbraco.Web/Routing/Segments/ContentSegmentProvider.cs @@ -1,23 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Web; +using Umbraco.Core; -namespace Umbraco.Core.ContentVariations +namespace Umbraco.Web.Routing.Segments { - - /// /// Returns the segment names to assign to the current request /// /// /// The provider also exposes via attributes which static segments can be applied to content variations /// - public abstract class ContentVariationProvider + public abstract class ContentSegmentProvider { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private IEnumerable _advertised; @@ -34,7 +31,7 @@ namespace Umbraco.Core.ContentVariations if (_advertised == null) { lck.UpgradeToWriteLock(); - _advertised = this.GetType().GetCustomAttributes(false) + _advertised = this.GetType().GetCustomAttributes(false) .Select(x => x.SegmentName) .Distinct() .ToArray(); @@ -45,13 +42,13 @@ namespace Umbraco.Core.ContentVariations } /// - /// Returns the segment names to assign to the current request + /// Returns the segment names and values to assign to the current request /// /// /// /// /// - public abstract IEnumerable GetSegmentsForRequest( + public abstract IDictionary GetSegmentsForRequest( Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest); diff --git a/src/Umbraco.Web/Routing/Segments/ContentSegmentProviderResolver.cs b/src/Umbraco.Web/Routing/Segments/ContentSegmentProviderResolver.cs new file mode 100644 index 0000000000..8ccde5ebf5 --- /dev/null +++ b/src/Umbraco.Web/Routing/Segments/ContentSegmentProviderResolver.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Web.Routing.Segments +{ + /// + /// Resolver for content variation providers + /// + public sealed class ContentSegmentProviderResolver : ManyObjectsResolverBase + { + /// + /// Initializes a new instance of the class with + /// an initial list of provider types. + /// + /// The list of provider types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal ContentSegmentProviderResolver(IEnumerable converters) + : base(converters) + { } + + /// + /// Initializes a new instance of the class with + /// an initial list of provider types. + /// + /// The list of provider types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal ContentSegmentProviderResolver(params Type[] converters) + : base(converters) + { } + + /// + /// Gets the providers. + /// + public IEnumerable Providers + { + get { return Values; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 03f5c397c9..e09ce61caf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -463,6 +463,11 @@ + + + + + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index f8a29d2a06..4602ff0a22 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -4,6 +4,7 @@ using System.Web; using Umbraco.Core; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Routing.Segments; using Umbraco.Web.Security; using umbraco; using umbraco.BusinessLogic; @@ -315,6 +316,21 @@ namespace Umbraco.Web } } + private RequestSegments _requestSegments; + + /// + /// Return the current request segments + /// + public RequestSegments RequestSegments + { + get + { + return _requestSegments ?? (_requestSegments = new RequestSegments( + ContentSegmentProviderResolver.Current.Providers, + OriginalRequestUrl, CleanedUmbracoUrl, HttpContext.Request)); + } + } + /// /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this /// object is instantiated which in the web site is created during the BeginRequest phase. diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 65112efce7..d9892ed1b5 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -31,6 +31,7 @@ using Umbraco.Web.PropertyEditors; using Umbraco.Web.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Routing.Segments; using Umbraco.Web.Security; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; @@ -292,6 +293,9 @@ namespace Umbraco.Web { base.InitializeResolvers(); + ContentSegmentProviderResolver.Current = new ContentSegmentProviderResolver( + typeof(BrowserCapabilitiesProvider)); + XsltExtensionsResolver.Current = new XsltExtensionsResolver(() => PluginManager.Current.ResolveXsltExtensions()); //set the default RenderMvcController