Gets installable custom variants showing up

This commit is contained in:
Shannon
2014-05-22 16:02:04 +10:00
parent 45aea76a11
commit 2b502e89f7
16 changed files with 171 additions and 110 deletions
@@ -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>
@@ -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>
+9 -5
View File
@@ -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>
+11 -6
View File
@@ -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
+1 -1
View File
@@ -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" />