Gets installable custom variants showing up
This commit is contained in:
@@ -8,29 +8,26 @@ using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentSegmentProviderTests
|
||||
{
|
||||
//[TestFixture]
|
||||
//public class ContentSegmentProviderTests
|
||||
//{
|
||||
|
||||
[Test]
|
||||
public void Get_Advertised_Segments()
|
||||
{
|
||||
var provider = new MyTestProvider();
|
||||
// [Test]
|
||||
// public void Get_Advertised_Segments()
|
||||
// {
|
||||
// var provider = new MyTestProvider();
|
||||
|
||||
Assert.AreEqual(3, provider.SegmentsAdvertised.Count());
|
||||
}
|
||||
// Assert.AreEqual(3, provider.SegmentKeysToPersist.Count());
|
||||
// }
|
||||
|
||||
[ContentSegment("Test1")]
|
||||
[ContentSegment("Test2")]
|
||||
[ContentSegment("Test3")]
|
||||
[ContentSegment("Test2")]
|
||||
private class MyTestProvider : ContentSegmentProvider
|
||||
{
|
||||
public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
{
|
||||
return new SegmentCollection(Enumerable.Empty<Segment>());
|
||||
}
|
||||
}
|
||||
|
||||
// private class MyTestProvider : ContentSegmentProvider
|
||||
// {
|
||||
// public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
// {
|
||||
// return new SegmentCollection(Enumerable.Empty<Segment>());
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<i class="icon" ng-class="item.isTrashed ? 'icon-trash' : item.exists ? 'icon-edit' : 'icon-add'"></i>
|
||||
|
||||
<span class="menu-label ng-binding">{{item.key}}</span>
|
||||
<span class="menu-label ng-binding">{{item.name}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
+4
-3
@@ -28,7 +28,10 @@ function segmentDashboardController($scope, umbRequestHelper, $log, $http, formH
|
||||
$scope.config.values = item.config;
|
||||
}
|
||||
|
||||
$scope.cancel = function() {
|
||||
$scope.cancel = function () {
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
$scope.configuring = false;
|
||||
$scope.config = {
|
||||
providerName: "",
|
||||
@@ -55,8 +58,6 @@ function segmentDashboardController($scope, umbRequestHelper, $log, $http, formH
|
||||
'Failed to save segment configuration')
|
||||
.then(function () {
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
//now that its successful, save the persisted values back to the provider's values
|
||||
var provider = _.find($scope.providers, function (i) { return i.typeName === $scope.config.providerTypeName; });
|
||||
provider.config = $scope.config.values;
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
<input type="text" name="value" ng-model="$parent.item.value" required />
|
||||
<span class="help-inline" val-msg-for="value" val-toggle-msg="required">Required</span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="Persist segment" alias="value" description="This might be useful if you want to track an assigned segment. For example, if it's a referral provider a segment might be set when coming from 'mysite.com' and you want to know about that later on when the segment no longer exists in the request.">
|
||||
<input type="checkbox" name="persist" ng-model="$parent.item.persist" />
|
||||
</umb-control-group>
|
||||
|
||||
</ng-form>
|
||||
|
||||
|
||||
@@ -99,12 +99,16 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
//if it's not a variant, then it's a master-doc so go lookup the possible variant types it can have
|
||||
|
||||
//These are the assignable segments based on the installed providers (statically advertised segments)
|
||||
var assignableSegments = ContentSegmentProviderResolver.Current.Providers.SelectMany(x => x.SegmentsAdvertised)
|
||||
.Select(x => new { segment = x, assigned = variantDef.ChildVariants.FirstOrDefault(k => k.Key == x) })
|
||||
//These are the assignable variants based on the installed providers (statically advertised variants)
|
||||
var assignableSegments = ContentSegmentProviderResolver.Current.Providers.SelectMany(x => x.AssignableContentVariants)
|
||||
.Select(x => new
|
||||
{
|
||||
segment = x,
|
||||
assigned = variantDef.ChildVariants.FirstOrDefault(k => k.Key == x.SegmentMatchKey)
|
||||
})
|
||||
.Select(x => x.assigned == null
|
||||
? new ContentVariableSegment(x.segment, false)
|
||||
: new ContentVariableSegment(x.segment, false, x.assigned.ChildId, x.assigned.IsTrashed));
|
||||
? new ContentVariableSegment(x.segment.VariantName, x.segment.SegmentMatchKey, false)
|
||||
: new ContentVariableSegment(x.segment.VariantName, x.segment.SegmentMatchKey, false, x.assigned.ChildId, x.assigned.IsTrashed));
|
||||
|
||||
//These are the lanuages assigned to this node (i.e. based on domains assigned to this node or ancestor nodes)
|
||||
var allDomains = DomainHelper.GetAllDomains(false);
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace Umbraco.Web.Editors
|
||||
.Select(x => new
|
||||
{
|
||||
typeName = x.GetType().FullName,
|
||||
displayName = ContentSegmentProvider.GetDisplayName(x),
|
||||
description = ContentSegmentProvider.GetDescription(x),
|
||||
displayName = x.Name,
|
||||
description = x.Description,
|
||||
asConfigurable = x as ConfigurableSegmentProvider
|
||||
})
|
||||
.Select(x => new
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Umbraco.Web.Models.Segments
|
||||
/// <param name="isLanguage"></param>
|
||||
public ContentVariableSegment(string key, bool isLanguage)
|
||||
{
|
||||
Name = key;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = false;
|
||||
@@ -29,6 +30,7 @@ namespace Umbraco.Web.Models.Segments
|
||||
/// <param name="isTrashed"></param>
|
||||
public ContentVariableSegment(string key, bool isLanguage, int variantId, bool isTrashed)
|
||||
{
|
||||
Name = key;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = true;
|
||||
@@ -36,6 +38,44 @@ namespace Umbraco.Web.Models.Segments
|
||||
IsTrashed = isTrashed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a variant that does not exist yet
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
public ContentVariableSegment(string name, string key, bool isLanguage)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// COnstructor for a variant that exists
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
/// <param name="variantId"></param>
|
||||
/// <param name="isTrashed"></param>
|
||||
public ContentVariableSegment(string name, string key, bool isLanguage, int variantId, bool isTrashed)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = true;
|
||||
VariantId = variantId;
|
||||
IsTrashed = isTrashed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The friendly name of the segment
|
||||
/// </summary>
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The segment Key associated with the content variant
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,10 @@ namespace Umbraco.Web.Models.Segments
|
||||
/// <summary>
|
||||
/// Defines an assigned segment in a request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The serialization names are only one letter - this is intentional to keep the cookie size small
|
||||
/// </remarks>
|
||||
[DataContract(Name = "s", Namespace = "")]
|
||||
public class Segment
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,7 +26,7 @@ namespace Umbraco.Web.Models.Segments
|
||||
Value = value;
|
||||
}
|
||||
|
||||
internal Segment(string key, object value, bool persist)
|
||||
public Segment(string key, object value, bool persist)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
@@ -48,19 +52,20 @@ namespace Umbraco.Web.Models.Segments
|
||||
/// <summary>
|
||||
/// The name of the segment
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
[DataMember(Name = "k", IsRequired = true)]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the segment
|
||||
/// </summary>
|
||||
public object Value { get; private set; }
|
||||
[DataMember(Name = "v")]
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this segment is to be persisted (default is false)
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
internal bool Persist { get; private set; }
|
||||
[DataMember(Name = "p")]
|
||||
public bool Persist { get; set; }
|
||||
|
||||
//TODO: We should make use of these expiry settings!
|
||||
|
||||
|
||||
@@ -25,5 +25,15 @@ namespace Umbraco.Web.Models.Segments
|
||||
/// </summary>
|
||||
[DataMember(Name = "matchExpression")]
|
||||
public string MatchExpression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag of whether or not to persist the segment match to the user's cookies (and to the member profile if they are logged in)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might be useful if you want to track a segment from a provider, perhaps if it's a referral provider a segment might be set
|
||||
/// when coming from mysite.com and you want to know about that later on when the segment no longer exists in the request.
|
||||
/// </remarks>
|
||||
[DataMember(Name = "persist")]
|
||||
public bool Persist { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Umbraco.Web.Routing.Segments
|
||||
/// </summary>
|
||||
[DisplayName("Browser capabilities provider")]
|
||||
[Description("Uses ASP.Net HttpBrowserCapabilities object to analyze the current request")]
|
||||
[ContentVariant("Mobile users", "IsMobileDevice")]
|
||||
internal class BrowserCapabilitiesProvider : ContentSegmentProvider
|
||||
{
|
||||
public BrowserCapabilitiesProvider()
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Umbraco.Web.Routing.Segments
|
||||
var config = ReadConfiguration();
|
||||
var result = config
|
||||
.Where(match => IsMatch(match.MatchExpression, cleanedRequestUrl, httpRequest))
|
||||
.Select(match => new Segment(match.Key, match.Value));
|
||||
.Select(match => new Segment(match.Key, match.Value, match.Persist));
|
||||
|
||||
return new SegmentCollection(result);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class ContentSegmentAttribute : Attribute
|
||||
{
|
||||
public string SegmentName { get; private set; }
|
||||
|
||||
public ContentSegmentAttribute(string segmentName)
|
||||
{
|
||||
SegmentName = segmentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,45 +17,30 @@ namespace Umbraco.Web.Routing.Segments
|
||||
/// </remarks>
|
||||
public abstract class ContentSegmentProvider
|
||||
{
|
||||
|
||||
public static string GetDisplayName(ContentSegmentProvider provider)
|
||||
protected ContentSegmentProvider()
|
||||
{
|
||||
var type = provider.GetType();
|
||||
var att = type.GetCustomAttribute<DisplayNameAttribute>(false);
|
||||
return att == null ? type.FullName : att.DisplayName;
|
||||
//ensure attributes exist
|
||||
var type = GetType();
|
||||
var nameAtt = type.GetCustomAttribute<DisplayNameAttribute>(false);
|
||||
var descAtt = type.GetCustomAttribute<DescriptionAttribute>(false);
|
||||
if (nameAtt == null || descAtt == null)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
"The segment provider " + type + " must be attributed with two attributes: " + typeof(DisplayNameAttribute) + " and " + typeof(DescriptionAttribute));
|
||||
}
|
||||
Name = nameAtt.DisplayName;
|
||||
Description = descAtt.Description;
|
||||
}
|
||||
|
||||
public static string GetDescription(ContentSegmentProvider provider)
|
||||
{
|
||||
var att = provider.GetType().GetCustomAttribute<DescriptionAttribute>(false);
|
||||
return att == null ? string.Empty : att.Description;
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
private IEnumerable<string> _advertised;
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the advertised segments for this, these can be used for content variants and are also the segments that
|
||||
/// will be persisted to storage (cookie and membership provider) if they match the segment names returned in the
|
||||
/// GetSegmentsForRequest method
|
||||
/// Returns the Content Variants that this provider supports
|
||||
/// </summary>
|
||||
public IEnumerable<string> SegmentsAdvertised
|
||||
public IEnumerable<ContentVariantAttribute> AssignableContentVariants
|
||||
{
|
||||
get
|
||||
{
|
||||
using (var lck = new UpgradeableReadLock(_lock))
|
||||
{
|
||||
if (_advertised == null)
|
||||
{
|
||||
lck.UpgradeToWriteLock();
|
||||
_advertised = this.GetType().GetCustomAttributes<ContentSegmentAttribute>(false)
|
||||
.Select(x => x.SegmentName)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
return _advertised;
|
||||
}
|
||||
get { return GetType().GetCustomAttributes<ContentVariantAttribute>(false).ToArray(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a custom Content Variant exposed by a segment provider
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class ContentVariantAttribute : Attribute
|
||||
{
|
||||
public string VariantName { get; set; }
|
||||
public string SegmentMatchKey { get; set; }
|
||||
public object SegmentMatchValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used to specify the content variant name and the segment key to match, in
|
||||
/// order for the request to match on this variant the value stored against the segmentMatchKey must
|
||||
/// be a boolean and must be true.
|
||||
/// </summary>
|
||||
/// <param name="variantName"></param>
|
||||
/// <param name="segmentMatchKey"></param>
|
||||
public ContentVariantAttribute(string variantName, string segmentMatchKey)
|
||||
{
|
||||
VariantName = variantName;
|
||||
SegmentMatchKey = segmentMatchKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used to specify the content variant name and the segment key to match, in
|
||||
/// order for the request to match on this variant the value stored against the segmentMatchKey must
|
||||
/// be equal to the segmentMatchValue specified
|
||||
/// </summary>
|
||||
/// <param name="variantName"></param>
|
||||
/// <param name="segmentMatchKey"></param>
|
||||
/// <param name="segmentMatchValue"></param>
|
||||
public ContentVariantAttribute(string variantName, string segmentMatchKey, object segmentMatchValue)
|
||||
{
|
||||
VariantName = variantName;
|
||||
SegmentMatchKey = segmentMatchKey;
|
||||
SegmentMatchValue = segmentMatchValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,37 +64,23 @@ namespace Umbraco.Web.Routing.Segments
|
||||
//NOTE: The cookie data will alraedy be part of the PersistedSegments (see GetAllSegmentsForRequest)
|
||||
|
||||
var toPersist = PersistedSegments.ToList();
|
||||
//var cookieData = request.Cookies[Constants.Web.SegmentCookieName] == null
|
||||
// ? new List<Segment>()
|
||||
// //TODO: try/catch
|
||||
// : JsonConvert.DeserializeObject<IEnumerable<Segment>>(request.Cookies[Constants.Web.SegmentCookieName].Value);
|
||||
|
||||
if (toPersist.Any())
|
||||
{
|
||||
////find anything in the cookie data that is not in our segments and add it to the segments to be persisted
|
||||
//foreach (var item in cookieData.Where(item => toPersist.Any(x => x.Name == item.Name) == false))
|
||||
//{
|
||||
// toPersist.Add(item);
|
||||
//}
|
||||
|
||||
|
||||
var json = JsonConvert.SerializeObject(toPersist);
|
||||
|
||||
//TODO: Implement the expiry dates in each individual segment - otherwise
|
||||
// what is going to happen is that any persisted segment will have a sliding expiration
|
||||
// of 30 days!
|
||||
|
||||
var cookie = new HttpCookie(Constants.Web.SegmentCookieName, json)
|
||||
{
|
||||
//sliding 30 day expiry?
|
||||
Expires = DateTime.Now.AddDays(30)
|
||||
};
|
||||
response.SetCookie(cookie);
|
||||
}
|
||||
//else if (cookieData.Any())
|
||||
//{
|
||||
// var json = JsonConvert.SerializeObject(toPersist);
|
||||
// var cookie = new HttpCookie(Constants.Web.SegmentCookieName, json)
|
||||
// {
|
||||
// //sliding 30 day expiry?
|
||||
// Expires = DateTime.Now.AddDays(30)
|
||||
// };
|
||||
// response.SetCookie(cookie);
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.SetCookie(new HttpCookie(Constants.Web.SegmentCookieName)
|
||||
@@ -197,8 +183,8 @@ namespace Umbraco.Web.Routing.Segments
|
||||
.Select(x => x.instance))
|
||||
{
|
||||
var segments = provider.GetSegmentsForRequest(originalRequestUrl, cleanedRequestUrl, httpRequest).ToArray();
|
||||
var advertised = provider.SegmentsAdvertised.ToArray();
|
||||
d.AddRange(segments.Select(s => new Segment(s.Key, s.Value, advertised.Contains(s.Key))));
|
||||
|
||||
d.AddRange(segments);
|
||||
}
|
||||
|
||||
var cookieData = httpRequest.Cookies[Constants.Web.SegmentCookieName] == null
|
||||
|
||||
@@ -470,7 +470,7 @@
|
||||
<Compile Include="Routing\Segments\RefererSegmentProvider.cs" />
|
||||
<Compile Include="Routing\Segments\RequestSegments.cs" />
|
||||
<Compile Include="Routing\Segments\BrowserCapabilitiesProvider.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentAttribute.cs" />
|
||||
<Compile Include="Routing\Segments\ContentVariantAttribute.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentProvider.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentProviderResolver.cs" />
|
||||
<Compile Include="Models\Segments\Segment.cs" />
|
||||
|
||||
Reference in New Issue
Block a user