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.

This commit is contained in:
Shannon
2014-05-13 19:26:37 +10:00
parent c3fe82a99d
commit 1e01d8272b
17 changed files with 372 additions and 98 deletions
-2
View File
@@ -274,8 +274,6 @@
<Compile Include="Configuration\UmbracoVersion.cs" />
<Compile Include="Attempt.cs" />
<Compile Include="Constants-Examine.cs" />
<Compile Include="ContentVariations\ContentVariationAttribute.cs" />
<Compile Include="ContentVariations\ContentVariationProvider.cs" />
<Compile Include="ControlExtensions.cs" />
<Compile Include="CoreBootManager.cs" />
<Compile Include="DatabaseContext.cs" />
@@ -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<string> GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
{
return Enumerable.Empty<string>();
}
}
}
}
@@ -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;
@@ -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<string, object> GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
{
return new Dictionary<string, object>();
}
}
}
}
@@ -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<HttpRequestBase>());
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<string, object> GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
{
return new Dictionary<string, object> { { "key1", false }, { "key2", "blahdd" }, { "key3", 9876 } };
}
}
private class MyTestProvider2 : ContentSegmentProvider
{
public override IDictionary<string, object> GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
{
return new Dictionary<string, object> {{"key1", true}, {"key2", "blah"}, {"key4", 123}};
}
}
}
}
@@ -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
{
@@ -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
{
+2 -1
View File
@@ -169,7 +169,7 @@
<Compile Include="AngularIntegration\JsInitializationTests.cs" />
<Compile Include="AngularIntegration\ServerVariablesParserTests.cs" />
<Compile Include="AttemptTests.cs" />
<Compile Include="ContentVariations\ContentVariationProviderTests.cs" />
<Compile Include="Routing\ContentSegmentProviderTests.cs" />
<Compile Include="Membership\DynamicMemberContentTests.cs" />
<Compile Include="Membership\MembershipProviderBaseTests.cs" />
<Compile Include="Membership\UmbracoServiceMembershipProviderTests.cs" />
@@ -317,6 +317,7 @@
<Compile Include="PublishedContent\StronglyTypedModels\Textpage.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
<Compile Include="Routing\ContentSegmentsTests.cs" />
<Compile Include="Services\LocalizationServiceTests.cs" />
<Compile Include="Resolvers\XsltExtensionsResolverTests.cs" />
<Compile Include="Services\MemberServiceTests.cs" />
+100
View File
@@ -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<ContentSegmentProvider> _segmentProviders;
private readonly Lazy<IDictionary<string, object>> _assignedSegments;
public RequestSegments(IEnumerable<ContentSegmentProvider> segmentProviders,
Uri originalRequestUrl,
Uri cleanedRequestUrl,
HttpRequestBase httpRequest)
{
_segmentProviders = segmentProviders;
_assignedSegments = new Lazy<IDictionary<string, object>>(() =>
GetAllSegmentsForRequest(
_segmentProviders,
originalRequestUrl,
cleanedRequestUrl,
httpRequest));
}
/// <summary>
/// Returns the assigned segments for the current request
/// </summary>
public IDictionary<string, object> AssignedSegments
{
get { return _assignedSegments.Value; }
}
/// <summary>
/// Returns true if any assigned segment value that is a boolean is set to true that matches the specified key
/// </summary>
/// <param name="segmentKey"></param>
/// <returns></returns>
/// <remarks>
/// Example: RequestIs("Mobile") if a segment key is "Mobile" and it's value is a boolean true.
/// </remarks>
public bool RequestIs(string segmentKey)
{
return AssignedSegments.Any(x => x.Key == segmentKey && x.Value is bool && (bool)x.Value);
}
/// <summary>
/// Returns true if any assigned segment has a value equal to the one specified
/// </summary>
/// <param name="segmentVal"></param>
/// <returns></returns>
public bool RequestContains(string segmentVal)
{
return AssignedSegments.Any(x => x.Value.ToString() == segmentVal);
}
/// <summary>
/// Returns true if any assigned segment key + value matches the specified parameters
/// </summary>
/// <param name="key"></param>
/// <param name="val"></param>
/// <returns></returns>
public bool RequestEquals(string key, object val)
{
return AssignedSegments.Any(x => x.Key == key && x.Value == val);
}
/// <summary>
/// Internal so it can be tested
/// </summary>
/// <param name="segmentProviders"></param>
/// <param name="originalRequestUrl"></param>
/// <param name="cleanedRequestUrl"></param>
/// <param name="httpRequest"></param>
/// <returns></returns>
internal static IDictionary<string, object> GetAllSegmentsForRequest(
IEnumerable<ContentSegmentProvider> 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<string, object>();
foreach (var keyVal in allKeyVals)
{
d[keyVal.Key] = keyVal.Value;
}
return d;
}
}
}
+68 -45
View File
@@ -1,77 +1,100 @@
using System;
using System.Collections.Generic;
using System.ServiceModel;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides context for the routing of a request.
/// </summary>
/// <summary>
/// Provides context for the routing of a request.
/// </summary>
public class RoutingContext
{
private readonly Lazy<UrlProvider> _urlProvider;
private readonly Lazy<IEnumerable<IContentFinder>> _publishedContentFinders;
private readonly Lazy<IContentFinder> _publishedContentLastChanceFinder;
private readonly Lazy<UrlProvider> _urlProvider;
private readonly Lazy<IEnumerable<IContentFinder>> _publishedContentFinders;
private readonly Lazy<IContentFinder> _publishedContentLastChanceFinder;
/// <summary>
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
/// </summary>
/// <param name="umbracoContext"> </param>
/// <param name="contentFinders">The document lookups resolver.</param>
/// <param name="contentLastChanceFinder"> </param>
/// <param name="urlProvider">The nice urls provider.</param>
internal RoutingContext(
UmbracoContext umbracoContext,
IEnumerable<IContentFinder> contentFinders,
IContentFinder contentLastChanceFinder,
/// <summary>
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
/// </summary>
/// <param name="umbracoContext"> </param>
/// <param name="contentFinders">The document lookups resolver.</param>
/// <param name="contentLastChanceFinder"> </param>
/// <param name="urlProvider">The nice urls provider.</param>
internal RoutingContext(
UmbracoContext umbracoContext,
IEnumerable<IContentFinder> contentFinders,
IContentFinder contentLastChanceFinder,
UrlProvider urlProvider)
: this(umbracoContext)
{
UmbracoContext = umbracoContext;
_publishedContentFinders = new Lazy<IEnumerable<IContentFinder>>(() => contentFinders, false);
_publishedContentLastChanceFinder = new Lazy<IContentFinder>(() => contentLastChanceFinder, false);
_urlProvider = new Lazy<UrlProvider>(() => urlProvider, false);
_urlProvider = new Lazy<UrlProvider>(() => urlProvider, false);
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="contentFinders"></param>
/// <param name="contentLastChanceFinder"></param>
/// <param name="urlProvider"></param>
internal RoutingContext(
UmbracoContext umbracoContext,
Lazy<IEnumerable<IContentFinder>> contentFinders,
Lazy<IContentFinder> contentLastChanceFinder,
Lazy<UrlProvider> urlProvider)
: this(umbracoContext)
{
UmbracoContext = umbracoContext;
_publishedContentFinders = contentFinders;
_publishedContentLastChanceFinder = contentLastChanceFinder;
_urlProvider = urlProvider;
}
/// <summary>
/// Gets the Umbraco context.
/// </summary>
public UmbracoContext UmbracoContext { get; private set; }
/// <summary>
/// Gets the published content finders.
/// </summary>
internal IEnumerable<IContentFinder> PublishedContentFinders
{
get { return _publishedContentFinders.Value; }
/// <summary>
/// Creates the lazy assigned segments logic
/// </summary>
/// <param name="umbracoContext"></param>
private RoutingContext(UmbracoContext umbracoContext)
{
UmbracoContext = umbracoContext;
}
/// <summary>
/// Gets the published content last chance finder.
/// </summary>
internal IContentFinder PublishedContentLastChanceFinder
{
/// <summary>
/// Gets the Umbraco context.
/// </summary>
public UmbracoContext UmbracoContext { get; private set; }
/// <summary>
/// Gets the published content finders.
/// </summary>
internal IEnumerable<IContentFinder> PublishedContentFinders
{
get { return _publishedContentFinders.Value; }
}
/// <summary>
/// Gets the published content last chance finder.
/// </summary>
internal IContentFinder PublishedContentLastChanceFinder
{
get { return _publishedContentLastChanceFinder.Value; }
}
}
/// <summary>
/// Gets the urls provider.
/// </summary>
public UrlProvider UrlProvider
{
get { return _urlProvider.Value; }
}
/// <summary>
/// Gets the urls provider.
/// </summary>
public UrlProvider UrlProvider
{
get { return _urlProvider.Value; }
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
namespace Umbraco.Web.Routing.Segments
{
/// <summary>
/// Assigns segments based on MS's HttpBrowserCapabilities object
/// </summary>
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<string, object> 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);
}
}
}
@@ -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;
}
@@ -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
{
/// <summary>
/// Returns the segment names to assign to the current request
/// </summary>
/// <remarks>
/// The provider also exposes via attributes which static segments can be applied to content variations
/// </remarks>
public abstract class ContentVariationProvider
public abstract class ContentSegmentProvider
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private IEnumerable<string> _advertised;
@@ -34,7 +31,7 @@ namespace Umbraco.Core.ContentVariations
if (_advertised == null)
{
lck.UpgradeToWriteLock();
_advertised = this.GetType().GetCustomAttributes<ContentVariationAttribute>(false)
_advertised = this.GetType().GetCustomAttributes<ContentSegmentAttribute>(false)
.Select(x => x.SegmentName)
.Distinct()
.ToArray();
@@ -45,13 +42,13 @@ namespace Umbraco.Core.ContentVariations
}
/// <summary>
/// Returns the segment names to assign to the current request
/// Returns the segment names and values to assign to the current request
/// </summary>
/// <param name="originalRequestUrl"></param>
/// <param name="cleanedRequestUrl"></param>
/// <param name="httpRequest"></param>
/// <returns></returns>
public abstract IEnumerable<string> GetSegmentsForRequest(
public abstract IDictionary<string, object> GetSegmentsForRequest(
Uri originalRequestUrl,
Uri cleanedRequestUrl,
HttpRequestBase httpRequest);
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Web.Routing.Segments
{
/// <summary>
/// Resolver for content variation providers
/// </summary>
public sealed class ContentSegmentProviderResolver : ManyObjectsResolverBase<ContentSegmentProviderResolver, ContentSegmentProvider>
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentSegmentProviderResolver"/> class with
/// an initial list of provider types.
/// </summary>
/// <param name="converters">The list of provider types</param>
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
internal ContentSegmentProviderResolver(IEnumerable<Type> converters)
: base(converters)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ContentSegmentProviderResolver"/> class with
/// an initial list of provider types.
/// </summary>
/// <param name="converters">The list of provider types</param>
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
internal ContentSegmentProviderResolver(params Type[] converters)
: base(converters)
{ }
/// <summary>
/// Gets the providers.
/// </summary>
public IEnumerable<ContentSegmentProvider> Providers
{
get { return Values; }
}
}
}
+5
View File
@@ -463,6 +463,11 @@
<Compile Include="PropertyEditors\PropertyValueEditorWrapper.cs" />
<Compile Include="PublishedContentQuery.cs" />
<Compile Include="ImageCropperTemplateExtensions.cs" />
<Compile Include="Routing\RequestSegments.cs" />
<Compile Include="Routing\Segments\BrowserCapabilitiesProvider.cs" />
<Compile Include="Routing\Segments\ContentSegmentAttribute.cs" />
<Compile Include="Routing\Segments\ContentSegmentProvider.cs" />
<Compile Include="Routing\Segments\ContentSegmentProviderResolver.cs" />
<Compile Include="Routing\UrlProviderExtensions.cs" />
<Compile Include="Strategies\Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
<Compile Include="Strategies\Migrations\ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs" />
+16
View File
@@ -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;
/// <summary>
/// Return the current request segments
/// </summary>
public RequestSegments RequestSegments
{
get
{
return _requestSegments ?? (_requestSegments = new RequestSegments(
ContentSegmentProviderResolver.Current.Providers,
OriginalRequestUrl, CleanedUmbracoUrl, HttpContext.Request));
}
}
/// <summary>
/// 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.
+4
View File
@@ -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