Refactor - IPublishedContent, converters, fragments (cont)

This commit is contained in:
Stephan
2016-06-29 15:28:40 +02:00
parent 12e5372148
commit a8bb18ecae
26 changed files with 665 additions and 717 deletions
+7
View File
@@ -347,5 +347,12 @@ namespace Umbraco.Core
{
return items ?? Enumerable.Empty<T>();
}
// the .OfType<T>() filter is nice when there's only one type
// this is to support filtering with multiple types
public static IEnumerable<T> OfTypes<T>(this IEnumerable<T> contents, params Type[] types)
{
return contents.Where(x => types.Contains(x.GetType()));
}
}
}
@@ -19,61 +19,38 @@ namespace Umbraco.Core.Models.PublishedContent
#region Extend
private static IPublishedContent Unwrap(IPublishedContent content)
{
if (content == null) return null;
while (true)
{
var extended = content as PublishedContentExtended;
if (extended != null)
{
if (((IPublishedContentExtended)extended).HasAddedProperties) return extended;
content = extended.Unwrap();
continue;
}
var wrapped = content as PublishedContentWrapped;
if (wrapped != null)
{
content = wrapped.Unwrap();
continue;
}
return content;
}
}
internal static IPublishedContentExtended Extend(IPublishedContent content)
{
// first unwrap content down to the lowest possible level, ie either the deepest inner
// IPublishedContent or the first extended that has added properties. this is to avoid
// nesting extended objects as much as possible, so we try to re-extend that lowest
// object.
// object. Then extend. But do NOT create a model, else we would not be able to add
// properties. BEWARE means that whatever calls Extend MUST create the model.
var wrapped = content as PublishedContentExtended;
while (wrapped != null && ((IPublishedContentExtended)wrapped).HasAddedProperties == false)
wrapped = (content = wrapped.Unwrap()) as PublishedContentExtended;
// if the factory returns something else than content it means it has created
// a model, and then that model has to inherit from PublishedContentExtended,
// => implements the internal IPublishedContentExtended.
// here we assume that either the factory just created a model that implements
// IPublishedContentExtended and therefore does not need to be extended again,
// because it can carry the extra property - or that it did *not* create a
// model and therefore returned the original content unchanged.
var model = content.CreateModel();
IPublishedContent extended;
if (model == content) // == means the factory did not create a model
{
// so we have to extend
extended = new PublishedContentExtended(content);
}
else
{
// else we can use what the factory returned
extended = model;
}
// so extended should always implement IPublishedContentExtended, however if
// by mistake the factory returned a different object that does not implement
// IPublishedContentExtended (which would be an error), throw.
//
// see also PublishedContentExtensionsForModels.CreateModel
// NOTE
// could we lift that constraint and accept that models just be IPublishedContent?
// would then mean that we cannot assume a model is IPublishedContentExtended, so
// either it is, or we need to wrap it. so instead of having
// (Model:IPublishedContentExtended (IPublishedContent))
// we'd have
// (PublishedContentExtended (Model (IPublishedContent)))
// and it is that bad? any other consequences?
//
// would also allow the factory to cache the model (though that should really
// be done by the content cache, not by the factory).
var extended2 = extended as IPublishedContentExtended;
if (extended2 == null)
throw new Exception("Extended does not implement IPublishedContentExtended.");
return extended2;
return new PublishedContentExtended(Unwrap(content));
}
#endregion
@@ -26,18 +26,6 @@ namespace Umbraco.Core.Models.PublishedContent
var model = PublishedContentModelFactoryResolver.Current.Factory.CreateModel(content);
if (model == null)
throw new Exception("IPublishedContentFactory returned null.");
if (ReferenceEquals(model, content))
return content;
// at the moment, other parts of our code assume that all models will
// somehow implement IPublishedContentExtended and not just be IPublishedContent,
// so we'd better check this here to fail as soon as we can.
//
// see also PublishedContentExtended.Extend
var extended = model as IPublishedContentExtended;
if (extended == null)
throw new Exception("IPublishedContentFactory created an object that does not implement IPublishedContentModelExtended.");
return model;
}
@@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Core.Models.PublishedContent
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Represents a strongly-typed published content.
/// </summary>
/// <remarks>Every strongly-typed published content class should inherit from <c>PublishedContentModel</c>
/// (or inherit from a class that inherits from... etc.) so they are picked by the factory.</remarks>
public abstract class PublishedContentModel : PublishedContentExtended
public abstract class PublishedContentModel : PublishedContentWrapped
{
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentModel"/> class with
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Models.PublishedContent
PropertyEditorAlias = propertyType.PropertyEditorAlias;
}
/*
/// <summary>
/// Initializes a new instance of the <see cref="PublishedPropertyType"/> class with an existing <see cref="PublishedPropertyType"/>
/// and a new property type alias.
@@ -49,7 +50,7 @@ namespace Umbraco.Core.Models.PublishedContent
internal PublishedPropertyType(string propertyTypeAlias, PublishedPropertyType propertyType)
: this(propertyTypeAlias, propertyType.DataTypeId, propertyType.PropertyEditorAlias)
{ }
*/
/// <summary>
/// Initializes a new instance of the <see cref="PublishedPropertyType"/> class with a property type alias and a property editor alias.
@@ -68,6 +69,7 @@ namespace Umbraco.Core.Models.PublishedContent
: this(propertyTypeAlias, 0, propertyEditorAlias)
{ }
/*
/// <summary>
/// Initializes a new instance of the <see cref="PublishedPropertyType"/> class with a property type alias and a datatype definition.
/// </summary>
@@ -79,6 +81,7 @@ namespace Umbraco.Core.Models.PublishedContent
internal PublishedPropertyType(string propertyTypeAlias, IDataTypeDefinition dataTypeDefinition)
: this(propertyTypeAlias, dataTypeDefinition.Id, dataTypeDefinition.PropertyEditorAlias)
{ }
*/
/// <summary>
/// Initializes a new instance of the <see cref="PublishedPropertyType"/> class with a property type alias,
@@ -23,55 +23,60 @@ namespace Umbraco.Core.PropertyEditors
Type GetPropertyValueType(PublishedPropertyType propertyType);
/// <summary>
/// Gets the property cache level of a specified value.
/// Gets the property cache level.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>The property cache level of the specified value.</returns>
/// <returns>The property cache level.</returns>
PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType);
/// <summary>
/// Converts a property Data value to a Source value.
/// Converts a property source value to an intermediate value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="source">The data value.</param>
/// <param name="source">The source value.</param>
/// <param name="preview">A value indicating whether conversion should take place in preview mode.</param>
/// <returns>The result of the conversion.</returns>
/// <remarks>
/// <para>The converter should know how to convert a <c>null</c> raw value, meaning that no
/// value has been assigned to the property. The source value can be <c>null</c>.</para>
/// <para>With the XML cache, raw values come from the XML cache and therefore are strings.</para>
/// <para>With objects caches, raw values would come from the database and therefore be either
/// ints, DateTimes, or strings.</para>
/// <para>The converter should know how to convert a <c>null</c> source value, meaning that no
/// value has been assigned to the property. The intermediate value can be <c>null</c>.</para>
/// <para>With the XML cache, source values come from the XML cache and therefore are strings.</para>
/// <para>With objects caches, source values would come from the database and therefore be either
/// ints, DateTimes, decimals, or strings.</para>
/// <para>The converter should be prepared to handle both situations.</para>
/// <para>When raw values are strings, the converter must handle empty strings, whitespace
/// <para>When source values are strings, the converter must handle empty strings, whitespace
/// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve
/// whitespaces.</para>
/// </remarks>
object ConvertSourceToInter(PublishedPropertyType propertyType, object source, bool preview);
/// <summary>
/// Converts a property Source value to an Object value.
/// Converts a property intermediate value to an Object value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="referenceCacheLevel">fixme</param>
/// <param name="inter">The source value.</param>
/// <param name="preview">A value indicating whether conversion should take place in preview mode.</param>
/// <returns>The result of the conversion.</returns>
/// <remarks>The converter should know how to convert a <c>null</c> source value, or any source value
/// indicating that no value has been assigned to the property. It is up to the converter to determine
/// what to return in that case: either <c>null</c>, or the default value...</remarks>
object ConvertInterToObject(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview);
/// <summary>
/// Converts a property Source value to an XPath value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="referenceCacheLevel">fixme</param>
/// <param name="inter">The source value.</param>
/// <param name="referenceCacheLevel">The reference cache level.</param>
/// <param name="inter">The intermediate value.</param>
/// <param name="preview">A value indicating whether conversion should take place in preview mode.</param>
/// <returns>The result of the conversion.</returns>
/// <remarks>
/// <para>The converter should know how to convert a <c>null</c> source value, or any source value
/// <para>The converter should know how to convert a <c>null</c> intermediate value, or any intermediate value
/// indicating that no value has been assigned to the property. It is up to the converter to determine
/// what to return in that case: either <c>null</c>, or the default value...</para>
/// <para>The <paramref name="referenceCacheLevel"/> is passed to the converter so that it can be, in turn,
/// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage
/// the cache levels of property values. It is not meant to be used by the converter.</para>
/// </remarks>
object ConvertInterToObject(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview);
/// <summary>
/// Converts a property intermediate value to an XPath value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="referenceCacheLevel">The reference cache level.</param>
/// <param name="inter">The intermediate value.</param>
/// <param name="preview">A value indicating whether conversion should take place in preview mode.</param>
/// <returns>The result of the conversion.</returns>
/// <remarks>
/// <para>The converter should know how to convert a <c>null</c> intermediate value, or any intermediate value
/// indicating that no value has been assigned to the property. It is up to the converter to determine
/// what to return in that case: either <c>null</c>, or the default value...</para>
/// <para>If successful, the result should be either <c>null</c>, a string, or an <c>XPathNavigator</c>
@@ -79,6 +84,9 @@ namespace Umbraco.Core.PropertyEditors
/// up to the converter.</para>
/// <para>The converter may want to return an XML fragment that represent a part of the content tree,
/// but should pay attention not to create infinite loops that would kill XPath and XSLT.</para>
/// <para>The <paramref name="referenceCacheLevel"/> is passed to the converter so that it can be, in turn,
/// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage
/// the cache levels of property values. It is not meant to be used by the converter.</para>
/// </remarks>
object ConvertInterToXPath(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview);
}
@@ -23,14 +23,14 @@ namespace Umbraco.Tests.PublishedContent
base.Initialize();
// need to specify a different callback for testing
PublishedContentExtensions.GetPropertyAliasesAndNames = s =>
PublishedContentExtensions.GetPropertyAliasesAndNames = (services, alias) =>
{
var userFields = new Dictionary<string, string>()
{
{"property1", "Property 1"},
{"property2", "Property 2"}
};
if (s == "Child")
if (alias == "Child")
{
userFields.Add("property4", "Property 4");
}
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.PublishedContent
public void To_DataTable()
{
var doc = GetContent(true, 1);
var dt = doc.ChildrenAsTable();
var dt = doc.ChildrenAsTable(ApplicationContext.Services);
Assert.AreEqual(11, dt.Columns.Count);
Assert.AreEqual(3, dt.Rows.Count);
@@ -95,7 +95,7 @@ namespace Umbraco.Tests.PublishedContent
//change a doc type alias
((TestPublishedContent) doc.Children.ElementAt(0)).DocumentTypeAlias = "DontMatch";
var dt = doc.ChildrenAsTable("Child");
var dt = doc.ChildrenAsTable(ApplicationContext.Services, "Child");
Assert.AreEqual(11, dt.Columns.Count);
Assert.AreEqual(2, dt.Rows.Count);
@@ -111,7 +111,7 @@ namespace Umbraco.Tests.PublishedContent
public void To_DataTable_No_Rows()
{
var doc = GetContent(false, 1);
var dt = doc.ChildrenAsTable();
var dt = doc.ChildrenAsTable(ApplicationContext.Services);
//will return an empty data table
Assert.AreEqual(0, dt.Columns.Count);
Assert.AreEqual(0, dt.Rows.Count);
@@ -27,11 +27,6 @@ namespace Umbraco.Tests.PublishedContent
throw new NotImplementedException();
}
public IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
{
throw new NotImplementedException();
}
public void Resync()
{ }
}
@@ -279,7 +274,7 @@ namespace Umbraco.Tests.PublishedContent
#endregion
}
class PublishedContentStrong1 : PublishedContentExtended
class PublishedContentStrong1 : PublishedContentModel
{
public PublishedContentStrong1(IPublishedContent content)
: base(content)
@@ -297,7 +292,7 @@ namespace Umbraco.Tests.PublishedContent
public int AnotherValue => this.GetPropertyValue<int>("anotherValue");
}
class PublishedContentStrong2 : PublishedContentExtended
class PublishedContentStrong2 : PublishedContentModel
{
public PublishedContentStrong2(IPublishedContent content)
: base(content)
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
@@ -5,8 +6,9 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Plugins;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Tests.PublishedContent
{
@@ -293,7 +295,7 @@ namespace Umbraco.Tests.PublishedContent
var rVal = doc.GetPropertyValue("testRecursive", true);
var nullVal = doc.GetPropertyValue("DoNotFindThis", true);
Assert.AreEqual("This is the recursive val", rVal);
Assert.AreEqual("", nullVal);
Assert.AreEqual(null, nullVal);
}
[Test]
@@ -530,66 +532,60 @@ namespace Umbraco.Tests.PublishedContent
Assert.AreEqual(1174, result.Id);
}
[Test]
public void FragmentTest()
{
}
[Test]
public void DetachedProperty1()
public void FragmentProperty()
{
var type = new PublishedPropertyType("detached", Constants.PropertyEditors.IntegerAlias);
var prop = PublishedProperty.GetDetached(type.Detached(), "5548");
var pt = new PublishedPropertyType("detached", Constants.PropertyEditors.IntegerAlias);
var ct = new PublishedContentType(0, "alias", new[] { pt });
var prop = new PublishedFragmentProperty(pt, Guid.NewGuid(), false, PropertyCacheLevel.None, 5548);
Assert.IsInstanceOf<int>(prop.Value);
Assert.AreEqual(5548, prop.Value);
}
public void CreateDetachedContentSample()
public void Fragment1()
{
bool previewing = false;
var t = ContentTypesCache.Get(PublishedItemType.Content, "detachedSomething");
var type = ContentTypesCache.Get(PublishedItemType.Content, "detachedSomething");
var values = new Dictionary<string, object>();
var properties = t.PropertyTypes.Select(x =>
{
object value;
if (values.TryGetValue(x.PropertyTypeAlias, out value) == false) value = null;
return PublishedProperty.GetDetached(x.Detached(), value, previewing);
});
// and if you want some sort of "model" it's up to you really...
var c = new DetachedContent(properties);
var f = new PublishedFragment(type, Guid.NewGuid(), values, false);
}
public void CreatedDetachedContentInConverterSample()
[Test]
public void Fragment2()
{
// the converter args
PublishedPropertyType argPropertyType = null;
object argSource = null;
bool argPreview = false;
var pt1 = new PublishedPropertyType("legend", 0, Constants.PropertyEditors.TextboxAlias);
var pt2 = new PublishedPropertyType("image", 0, Constants.PropertyEditors.MediaPickerAlias);
string val1 = "";
int val2 = 0;
var pt3 = new PublishedPropertyType("size", 0, Constants.PropertyEditors.IntegerAlias);
const string val1 = "boom bam";
const int val2 = 0;
const int val3 = 666;
var c = new ImageWithLegendModel(
PublishedProperty.GetDetached(pt1.Nested(argPropertyType), val1, argPreview),
PublishedProperty.GetDetached(pt2.Nested(argPropertyType), val2, argPreview));
var guid = Guid.NewGuid();
var ct = new PublishedContentType(0, "alias", new[] { pt1, pt2, pt3 });
var c = new ImageWithLegendModel(ct, guid, new Dictionary<string, object>
{
{ "legend", val1 },
{ "image", val2 },
{ "size", val3 },
}, false);
Assert.AreEqual(val1, c.Legend);
Assert.AreEqual(val3, c.Size);
}
class ImageWithLegendModel
class ImageWithLegendModel : PublishedFragment
{
private readonly IPublishedProperty _legendProperty;
private readonly IPublishedProperty _imageProperty;
public ImageWithLegendModel(PublishedContentType contentType, Guid fragmentKey, Dictionary<string, object> values, bool previewing)
: base(contentType, fragmentKey, values, previewing)
{ }
public ImageWithLegendModel(IPublishedProperty legendProperty, IPublishedProperty imageProperty)
{
_legendProperty = legendProperty;
_imageProperty = imageProperty;
}
public string Legend => _legendProperty.GetValue<string>();
public IPublishedContent Image => _imageProperty.GetValue<IPublishedContent>();
public string Legend => this.GetPropertyValue<string>("legend");
public IPublishedContent Image => this.GetPropertyValue<IPublishedContent>("image");
public int Size => this.GetPropertyValue<int>("size");
}
}
}
+4 -5
View File
@@ -16,9 +16,8 @@ namespace Umbraco.Web
{
internal static IEnumerable<IPublishedContent> ConvertSearchResultToPublishedContent(this IEnumerable<SearchResult> results, IPublishedCache cache)
{
//TODO: The search result has already returned a result which SHOULD include all of the data to create an IPublishedContent,
// however this is currently not the case:
// http://examine.codeplex.com/workitem/10350
// no need to think about creating the IPublishedContent from the Examine result
// content cache is fast and optimized - use it!
var list = new List<IPublishedContent>();
@@ -35,13 +34,13 @@ namespace Umbraco.Web
// returned by the cache, in case the cache can create real types.
// so we have to ask it to please extend itself.
//var extend = set.MapContent(content);
var extend = PublishedContentExtended.Extend(content);
list.Add(extend);
list.Add(extend.CreateModel()); // take care, must create the model!
var property = new PropertyResult("examineScore",
result.Score,
PropertyResultType.CustomProperty);
extend.AddProperty(property);
}
@@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
// fixme
// this is an experimental converter to work with nested etc.
internal class TempValueConverter : PropertyValueConverterBase
{
private readonly IFacadeAccessor _facadeAccessor;
public TempValueConverter(IFacadeAccessor facadeAccessor)
{
_facadeAccessor = facadeAccessor;
}
public override bool IsConverter(PublishedPropertyType propertyType)
{
return propertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.ContentPickerAlias);
}
public override Type GetPropertyValueType(PublishedPropertyType propertyType)
{
return typeof (IPublishedFragment);
}
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
{
return PropertyCacheLevel.Snapshot;
}
public override object ConvertSourceToInter(PublishedPropertyType propertyType, object source, bool preview)
{
var json = source as string;
return json == null ? null : JsonConvert.DeserializeObject<TempData>(json);
}
public override object ConvertInterToObject(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
var data = (TempData) inter;
if (data == null) return null;
var contentType = _facadeAccessor.Facade.ContentCache.GetContentType(data.ContentTypeAlias);
if (contentType == null) return null;
// fixme
// note: if we wanted to returned a strongly-typed model here, we'd have to be explicit about it
// so that we can tell GetPropertyValueType what we return - just relying on a factory is not
// going to make it in any helpful way?
return new PublishedFragment(contentType, _facadeAccessor, referenceCacheLevel, data.Key, data.Values, preview);
}
public override object ConvertInterToXPath(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
return inter == null ? null : JsonConvert.SerializeObject((TempData) inter);
}
public class TempData
{
public string ContentTypeAlias { get; set; }
public Guid Key { get; set; }
public Dictionary<string, object> Values { get; set; }
}
}
}
@@ -1,23 +1,27 @@
using System;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache
{
abstract class FacadeServiceBase : IFacadeService
{
private IFacadeAccessor _facadeAccessor;
protected FacadeServiceBase(IFacadeAccessor facadeAccessor)
{
_facadeAccessor = facadeAccessor;
FacadeAccessor = facadeAccessor;
}
public IFacadeAccessor FacadeAccessor { get; }
// note: NOT setting _facadeAccessor.Facade here because it is the
// responsibility of the caller to manage what the 'current' facade is
public abstract IFacade CreateFacade(string previewToken);
protected IFacade CurrentFacade => _facadeAccessor.Facade;
protected IFacade CurrentFacade => FacadeAccessor.Facade;
public abstract IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null);
public abstract string EnterPreview(IUser user, int contentId);
public abstract void RefreshPreview(string previewToken, int contentId);
-12
View File
@@ -39,17 +39,5 @@ namespace Umbraco.Web.PublishedCache
/// otherwise the facade keeps previewing according to whatever settings it is using already.</para>
/// <para>Stops forcing preview when disposed.</para></remarks>
IDisposable ForcedPreview(bool preview, Action<bool> callback = null);
// fixme - document
/// <summary>
/// Creates a fragment property.
/// </summary>
/// <param name="propertyType"></param>
/// <param name="itemKey"></param>
/// <param name="previewing"></param>
/// <param name="referenceCacheLevel"></param>
/// <param name="sourceValue"></param>
/// <returns></returns>
IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null);
}
}
@@ -3,7 +3,7 @@
/// <summary>
/// Provides access to IFacade.
/// </summary>
interface IFacadeAccessor
public interface IFacadeAccessor
{
IFacade Facade { get; set; }
}
@@ -1,5 +1,7 @@
using System;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache
@@ -9,7 +11,7 @@ namespace Umbraco.Web.PublishedCache
/// </summary>
public interface IFacadeService : IDisposable
{
#region PublishedCaches
#region Facade
/* Various places (such as Node) want to access the XML content, today as an XmlDocument
* but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need
@@ -37,6 +39,11 @@ namespace Umbraco.Web.PublishedCache
/// <returns>A facade.</returns>
IFacade CreateFacade(string previewToken);
/// <summary>
/// Gets the facade accessor.
/// </summary>
IFacadeAccessor FacadeAccessor { get; }
#endregion
#region Preview
@@ -136,5 +143,20 @@ namespace Umbraco.Web.PublishedCache
void Notify(DomainCacheRefresher.JsonPayload[] payloads);
#endregion
#region Fragment
/// <summary>
/// Creates a fragment property.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="itemKey">The fragment key.</param>
/// <param name="previewing">A value indicating whether previewing.</param>
/// <param name="referenceCacheLevel">The reference cache level.</param>
/// <param name="sourceValue">The source value.</param>
/// <returns>A fragment property.</returns>
IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null);
#endregion
}
}
@@ -96,11 +96,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
public IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
{
return _service.CreateFragmentProperty(propertyType, itemKey, previewing, referenceCacheLevel, sourceValue);
}
#endregion
#region IDisposable
@@ -1482,7 +1482,7 @@ AND cmsContentNu.nodeId IS NULL
#region Fragments
public IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
public override IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
{
return new PublishedFragmentProperty(_facadeAccessor, propertyType, itemKey, previewing, referenceCacheLevel, sourceValue);
}
@@ -5,21 +5,21 @@ using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PublishedCache.NuCache
{
internal class PublishedFragmentProperty : PublishedCache.PublishedFragmentProperty
internal class PublishedFragmentProperty : PublishedFragmentPropertyBase
{
private readonly IFacadeAccessor _facadeAccessor;
private string _valuesCacheKey;
// initializes a published item property
public PublishedFragmentProperty(IFacadeAccessor facadeAccessor, PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
: base(propertyType, itemKey, previewing, referenceCacheLevel, sourceValue)
public PublishedFragmentProperty(IFacadeAccessor facadeAccessor, PublishedPropertyType propertyType, Guid fragmentKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
: base(propertyType, fragmentKey, previewing, referenceCacheLevel, sourceValue)
{
_facadeAccessor = facadeAccessor;
}
// used to cache the CacheValues of this property
internal string ValuesCacheKey => _valuesCacheKey
?? (_valuesCacheKey = CacheKeys.PropertyCacheValues(ItemUid, PropertyTypeAlias, IsPreviewing));
?? (_valuesCacheKey = CacheKeys.PropertyCacheValues(FragmentKey, PropertyTypeAlias, IsPreviewing));
protected override CacheValues GetSnapshotCacheValues()
{
@@ -6,36 +6,27 @@ using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PublishedCache
{
// fixme document & review
// notes:
// a published fragment does NOT manage any tree-like elements, neither the
// original NestedContent (from Lee) nor the DetachedPublishedContent POC did.
//
// these things should NOT manage any tree-like elements (fixme:discuss?)
// Lee's NestedContent package DetachedPublishedContent does NOT
// and yet we need to be able to figure out what happens when nesting things?
//
// how would we create MODELS for published items? should it be automatic
// or explicit when creating the items? probably cannot be automatic because
// then, how would we cast the returned object?
//
// note: could also have a totally detached one in Core?
// at the moment we do NOT support models for fragments - that would require
// an entirely new models factory + not even sure it makes sense at all since
// fragments are created manually
//
internal class PublishedFragment : IPublishedFragment
{
private readonly IFacadeAccessor _facadeAccessor;
// initializes a new instance of the PublishedFragment class
// within the context of a facade service (eg a published content property value)
public PublishedFragment(PublishedContentType contentType,
IFacadeAccessor facadeAccessor, PropertyCacheLevel referenceCacheLevel,
IFacadeService facadeService, PropertyCacheLevel referenceCacheLevel,
Guid key, Dictionary<string, object> values,
bool previewing)
{
ContentType = contentType;
_facadeAccessor = facadeAccessor;
Key = key;
// ensure we ignore case for property aliases
var comparer = values.Comparer;
var ignoreCase = comparer == StringComparer.OrdinalIgnoreCase || comparer == StringComparer.InvariantCultureIgnoreCase || comparer == StringComparer.CurrentCultureIgnoreCase;
if (ignoreCase == false)
values = new Dictionary<string, object>(values, StringComparer.OrdinalIgnoreCase);
values = GetCaseInsensitiveValueDictionary(values);
_propertiesArray = contentType
.PropertyTypes
@@ -43,10 +34,42 @@ namespace Umbraco.Web.PublishedCache
{
object value;
values.TryGetValue(propertyType.PropertyTypeAlias, out value);
return _facadeAccessor.Facade.CreateFragmentProperty(propertyType, Key, previewing, referenceCacheLevel, value);
return facadeService.CreateFragmentProperty(propertyType, Key, previewing, referenceCacheLevel, value);
})
.ToArray();
}
// initializes a new instance of the PublishedFragment class
// without any context, so it's purely 'standalone' and should NOT interfere with the facade service
public PublishedFragment(PublishedContentType contentType, Guid key, Dictionary<string, object> values, bool previewing)
{
ContentType = contentType;
Key = key;
values = GetCaseInsensitiveValueDictionary(values);
// using an initial reference cache level of .None ensures that
// everything will be cached at .Content level
// that reference cache level will propagate to all properties
const PropertyCacheLevel cacheLevel = PropertyCacheLevel.None;
_propertiesArray = contentType
.PropertyTypes
.Select(propertyType =>
{
object value;
values.TryGetValue(propertyType.PropertyTypeAlias, out value);
return (IPublishedProperty) new PublishedFragmentProperty(propertyType, Key, previewing, cacheLevel, value);
})
.ToArray();
}
private static Dictionary<string, object> GetCaseInsensitiveValueDictionary(Dictionary<string, object> values)
{
// ensure we ignore case for property aliases
var comparer = values.Comparer;
var ignoreCase = comparer == StringComparer.OrdinalIgnoreCase || comparer == StringComparer.InvariantCultureIgnoreCase || comparer == StringComparer.CurrentCultureIgnoreCase;
return ignoreCase ? values : new Dictionary<string, object>(values, StringComparer.OrdinalIgnoreCase);
}
#region ContentType
@@ -4,162 +4,20 @@ using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PublishedCache
{
internal abstract class PublishedFragmentProperty : PublishedPropertyBase
class PublishedFragmentProperty : PublishedFragmentPropertyBase
{
private readonly object _locko = new object();
private readonly object _sourceValue;
public PublishedFragmentProperty(PublishedPropertyType propertyType, Guid fragmentKey, bool previewing, PropertyCacheLevel cacheLevel, object sourceValue = null)
: base(propertyType, fragmentKey, previewing, cacheLevel, sourceValue)
{ }
protected readonly Guid ItemUid;
protected readonly bool IsPreviewing;
protected readonly bool IsMember;
private bool _interInitialized;
private object _interValue;
private CacheValues _cacheValues;
// initializes a published item property
protected PublishedFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
: base(propertyType, referenceCacheLevel)
protected override CacheValues GetSnapshotCacheValues()
{
_sourceValue = sourceValue;
ItemUid = itemKey;
IsPreviewing = previewing;
IsMember = propertyType.ContentType.ItemType == PublishedItemType.Member;
throw new NotSupportedException();
}
public override bool HasValue => _sourceValue != null
&& ((_sourceValue is string) == false || string.IsNullOrWhiteSpace((string)_sourceValue) == false);
protected class CacheValues
protected override CacheValues GetFacadeCacheValues()
{
public bool ObjectInitialized;
public object ObjectValue;
public bool XPathInitialized;
public object XPathValue;
}
private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel)
{
switch (cacheLevel)
{
case PropertyCacheLevel.Content:
case PropertyCacheLevel.Snapshot:
case PropertyCacheLevel.Facade:
case PropertyCacheLevel.None:
break;
default:
throw new Exception("Invalid cache level.");
}
}
private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel)
{
// based upon the current reference cache level (ReferenceCacheLevel) and this property
// cache level (PropertyType.CacheLevel), determines both the actual cache level for the
// property, and the new reference cache level.
// sanity checks
ValidateCacheLevel(ReferenceCacheLevel);
ValidateCacheLevel(PropertyType.CacheLevel);
// if the property cache level is 'shorter-termed' that the reference
// then use it and it becomes the new reference, else use Content and
// don't change the reference.
//
// examples:
// currently (reference) caching at facade, property specifies
// snapshot, ok to use content. OTOH, currently caching at snapshot,
// property specifies facade, need to use facade.
//
if (PropertyType.CacheLevel > ReferenceCacheLevel)
{
cacheLevel = PropertyType.CacheLevel;
referenceCacheLevel = cacheLevel;
}
else
{
cacheLevel = PropertyCacheLevel.Content;
referenceCacheLevel = ReferenceCacheLevel;
}
}
protected abstract CacheValues GetSnapshotCacheValues();
protected abstract CacheValues GetFacadeCacheValues();
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
{
CacheValues cacheValues;
switch (cacheLevel)
{
case PropertyCacheLevel.None:
// never cache anything
cacheValues = new CacheValues();
break;
case PropertyCacheLevel.Content:
// cache within the property object itself, ie within the content object
cacheValues = _cacheValues ?? (_cacheValues = new CacheValues());
break;
case PropertyCacheLevel.Snapshot:
// cache within the snapshot cache, depending...
cacheValues = GetSnapshotCacheValues();
break;
case PropertyCacheLevel.Facade:
// cache within the facade cache
cacheValues = GetFacadeCacheValues();
break;
default:
throw new InvalidOperationException("Invalid cache level.");
}
return cacheValues;
}
private object GetInterValue()
{
if (_interInitialized) return _interValue;
_interValue = PropertyType.ConvertSourceToInter(_sourceValue, IsPreviewing);
_interInitialized = true;
return _interValue;
}
public override object SourceValue => _sourceValue;
public override object Value
{
get
{
lock (_locko)
{
PropertyCacheLevel cacheLevel, referenceCacheLevel;
GetCacheLevels(out cacheLevel, out referenceCacheLevel);
var cacheValues = GetCacheValues(cacheLevel);
if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue;
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(referenceCacheLevel, GetInterValue(), IsPreviewing);
cacheValues.ObjectInitialized = true;
return cacheValues.ObjectValue;
}
}
}
public override object XPathValue
{
get
{
lock (_locko)
{
PropertyCacheLevel cacheLevel, referenceCacheLevel;
GetCacheLevels(out cacheLevel, out referenceCacheLevel);
var cacheValues = GetCacheValues(cacheLevel);
if (cacheValues.XPathInitialized) return cacheValues.XPathValue;
cacheValues.XPathValue = PropertyType.ConvertInterToXPath(referenceCacheLevel, GetInterValue(), IsPreviewing);
cacheValues.XPathInitialized = true;
return cacheValues.XPathValue;
}
}
throw new NotSupportedException();
}
}
}
}
@@ -0,0 +1,165 @@
using System;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PublishedCache
{
internal abstract class PublishedFragmentPropertyBase : PublishedPropertyBase
{
private readonly object _locko = new object();
private readonly object _sourceValue;
protected readonly Guid FragmentKey;
protected readonly bool IsPreviewing;
protected readonly bool IsMember;
private bool _interInitialized;
private object _interValue;
private CacheValues _cacheValues;
// initializes a published item property
protected PublishedFragmentPropertyBase(PublishedPropertyType propertyType, Guid fragmentKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
: base(propertyType, referenceCacheLevel)
{
_sourceValue = sourceValue;
FragmentKey = fragmentKey;
IsPreviewing = previewing;
IsMember = propertyType.ContentType.ItemType == PublishedItemType.Member;
}
public override bool HasValue => _sourceValue != null
&& ((_sourceValue is string) == false || string.IsNullOrWhiteSpace((string)_sourceValue) == false);
protected class CacheValues
{
public bool ObjectInitialized;
public object ObjectValue;
public bool XPathInitialized;
public object XPathValue;
}
private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel)
{
switch (cacheLevel)
{
case PropertyCacheLevel.Content:
case PropertyCacheLevel.Snapshot:
case PropertyCacheLevel.Facade:
case PropertyCacheLevel.None:
break;
default:
throw new Exception("Invalid cache level.");
}
}
private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel)
{
// based upon the current reference cache level (ReferenceCacheLevel) and this property
// cache level (PropertyType.CacheLevel), determines both the actual cache level for the
// property, and the new reference cache level.
// sanity checks
ValidateCacheLevel(ReferenceCacheLevel);
ValidateCacheLevel(PropertyType.CacheLevel);
// if the property cache level is 'shorter-termed' that the reference
// then use it and it becomes the new reference, else use Content and
// don't change the reference.
//
// examples:
// currently (reference) caching at facade, property specifies
// snapshot, ok to use content. OTOH, currently caching at snapshot,
// property specifies facade, need to use facade.
//
if (PropertyType.CacheLevel > ReferenceCacheLevel)
{
cacheLevel = PropertyType.CacheLevel;
referenceCacheLevel = cacheLevel;
}
else
{
cacheLevel = PropertyCacheLevel.Content;
referenceCacheLevel = ReferenceCacheLevel;
}
}
protected abstract CacheValues GetSnapshotCacheValues();
protected abstract CacheValues GetFacadeCacheValues();
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
{
CacheValues cacheValues;
switch (cacheLevel)
{
case PropertyCacheLevel.None:
// never cache anything
cacheValues = new CacheValues();
break;
case PropertyCacheLevel.Content:
// cache within the property object itself, ie within the content object
cacheValues = _cacheValues ?? (_cacheValues = new CacheValues());
break;
case PropertyCacheLevel.Snapshot:
// cache within the snapshot cache, depending...
cacheValues = GetSnapshotCacheValues();
break;
case PropertyCacheLevel.Facade:
// cache within the facade cache
cacheValues = GetFacadeCacheValues();
break;
default:
throw new InvalidOperationException("Invalid cache level.");
}
return cacheValues;
}
private object GetInterValue()
{
if (_interInitialized) return _interValue;
_interValue = PropertyType.ConvertSourceToInter(_sourceValue, IsPreviewing);
_interInitialized = true;
return _interValue;
}
public override object SourceValue => _sourceValue;
public override object Value
{
get
{
lock (_locko)
{
PropertyCacheLevel cacheLevel, referenceCacheLevel;
GetCacheLevels(out cacheLevel, out referenceCacheLevel);
var cacheValues = GetCacheValues(cacheLevel);
if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue;
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(referenceCacheLevel, GetInterValue(), IsPreviewing);
cacheValues.ObjectInitialized = true;
return cacheValues.ObjectValue;
}
}
}
public override object XPathValue
{
get
{
lock (_locko)
{
PropertyCacheLevel cacheLevel, referenceCacheLevel;
GetCacheLevels(out cacheLevel, out referenceCacheLevel);
var cacheValues = GetCacheValues(cacheLevel);
if (cacheValues.XPathInitialized) return cacheValues.XPathValue;
cacheValues.XPathValue = PropertyType.ConvertInterToXPath(referenceCacheLevel, GetInterValue(), IsPreviewing);
cacheValues.XPathInitialized = true;
return cacheValues.XPathValue;
}
}
}
}
}
@@ -57,12 +57,5 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
protected override void DisposeResources()
{ }
}
public IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
{
// fixme
throw new NotImplementedException();
}
}
}
@@ -1,10 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Cache;
@@ -200,5 +203,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
#endregion
#region Fragments
public override IPublishedProperty CreateFragmentProperty(PublishedPropertyType propertyType, Guid itemKey, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null)
{
throw new NotImplementedException();
}
#endregion
}
}
+60 -281
View File
@@ -9,202 +9,10 @@ using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Models;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web
{
// fixme! + migrate a few more of them
/// <summary>
/// Provides extension methods for <c>IPublishedItem</c>.
/// </summary>
public static class PublishedItemExtensions
{
#region IsComposedOf
/// <summary>
/// Gets a value indicating whether the content is of a content type composed of the given alias
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The content type alias.</param>
/// <returns>A value indicating whether the content is of a content type composed of a content type identified by the alias.</returns>
public static bool IsComposedOf(this IPublishedFragment content, string alias)
{
return content.ContentType.CompositionAliases.Contains(alias);
}
#endregion
#region HasProperty
/// <summary>
/// Gets a value indicating whether the content has a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has the property identified by the alias.</returns>
/// <remarks>The content may have a property, and that property may not have a value.</remarks>
public static bool HasProperty(this IPublishedFragment content, string alias)
{
return content.ContentType.GetPropertyType(alias) != null;
}
#endregion
#region HasValue
/// <summary>
/// Gets a value indicating whether the content has a value for a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has a value for the property identified by the alias.</returns>
/// <remarks>Returns true if <c>GetProperty(alias)</c> is not <c>null</c> and <c>GetProperty(alias).HasValue</c> is <c>true</c>.</remarks>
public static bool HasValue(this IPublishedFragment content, string alias)
{
var prop = content.GetProperty(alias);
return prop != null && prop.HasValue;
}
/// <summary>
/// Returns one of two strings depending on whether the content has a value for a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="valueIfTrue">The value to return if the content has a value for the property.</param>
/// <param name="valueIfFalse">The value to return if the content has no value for the property.</param>
/// <returns>Either <paramref name="valueIfTrue"/> or <paramref name="valueIfFalse"/> depending on whether the content
/// has a value for the property identified by the alias.</returns>
public static IHtmlString HasValue(this IPublishedFragment content, string alias,
string valueIfTrue, string valueIfFalse = null)
{
return content.HasValue(alias)
? new HtmlString(valueIfTrue)
: new HtmlString(valueIfFalse ?? string.Empty);
}
#endregion
#region GetPropertyValue
/// <summary>
/// Gets the value of a content's property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>The value of the content's property identified by the alias.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <c>null</c>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias)
{
var property = content.GetProperty(alias);
return property?.Value;
}
/// <summary>
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias, string defaultValue)
{
var property = content.GetProperty(alias);
return property == null || property.HasValue == false ? defaultValue : property.Value;
}
/// <summary>
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias, object defaultValue)
{
var property = content.GetProperty(alias);
return property == null || property.HasValue == false ? defaultValue : property.Value;
}
#endregion
#region GetPropertyValue<T>
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type.
/// </summary>
/// <typeparam name="T">The target property type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <c>default(T)</c>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static T GetPropertyValue<T>(this IPublishedFragment content, string alias)
{
return content.GetPropertyValue(alias, false, default(T));
}
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value.
/// </summary>
/// <typeparam name="T">The target property type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static T GetPropertyValue<T>(this IPublishedFragment content, string alias, T defaultValue)
{
return content.GetPropertyValue(alias, true, defaultValue);
}
internal static T GetPropertyValue<T>(this IPublishedFragment content, string alias, bool withDefaultValue, T defaultValue)
{
var property = content.GetProperty(alias);
if (property == null) return defaultValue;
return property.GetValue(withDefaultValue, defaultValue);
}
#endregion
#region ToIndexedArray
public static IndexedArrayItem<TContent>[] ToIndexedArray<TContent>(this IEnumerable<TContent> source)
where TContent : class, IPublishedFragment
{
var set = source.Select((content, index) => new IndexedArrayItem<TContent>(content, index)).ToArray();
foreach (var setItem in set) setItem.TotalCount = set.Length;
return set;
}
#endregion
}
/// <summary>
/// Provides extension methods for <c>IPublishedContent</c>.
/// </summary>
@@ -494,19 +302,19 @@ namespace Umbraco.Web
public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
return content.Where(predicate, valueIfTrue, string.Empty);
}
public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue, string valueIfFalse)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
return new HtmlString(content.Where(predicate) ? valueIfTrue : valueIfFalse);
}
public static bool Where(this IPublishedContent content, string predicate)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
var dynamicDocumentList = new DynamicPublishedContentList { content.AsDynamicOrNull() };
var filtered = dynamicDocumentList.Where<DynamicPublishedContent>(predicate);
return filtered.Count() == 1;
@@ -521,7 +329,7 @@ namespace Umbraco.Web
// content should NOT be null
public static dynamic AsDynamic(this IPublishedContent content)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
return new DynamicPublishedContent(content);
}
@@ -575,9 +383,7 @@ namespace Umbraco.Web
if (content.IsDocumentType(docTypeAlias))
return true;
if (recursive)
return IsDocumentTypeRecursive(content, docTypeAlias);
return false;
return recursive && IsDocumentTypeRecursive(content, docTypeAlias);
}
private static bool IsDocumentTypeRecursive(IPublishedContent content, string docTypeAlias)
@@ -996,7 +802,7 @@ namespace Umbraco.Web
/// <returns>Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc).</returns>
internal static IEnumerable<IPublishedContent> EnumerateAncestors(this IPublishedContent content, bool orSelf)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
if (orSelf) yield return content;
while ((content = content.Parent) != null)
yield return content;
@@ -1169,21 +975,19 @@ namespace Umbraco.Web
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content, bool orSelf)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
if (orSelf) yield return content;
foreach (var child in content.Children)
foreach (var child2 in child.EnumerateDescendants())
yield return child2;
foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants()))
yield return desc;
}
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content)
{
yield return content;
foreach (var child in content.Children)
foreach (var child2 in child.EnumerateDescendants())
yield return child2;
foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants()))
yield return desc;
}
#endregion
@@ -1202,7 +1006,7 @@ namespace Umbraco.Web
public static IPublishedContent Up(this IPublishedContent content, int number)
{
if (number < 0)
throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero.");
throw new ArgumentOutOfRangeException(nameof(number), "Must be greater than, or equal to, zero.");
return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault();
}
@@ -1225,7 +1029,7 @@ namespace Umbraco.Web
public static IPublishedContent Down(this IPublishedContent content, int number)
{
if (number < 0)
throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero.");
throw new ArgumentOutOfRangeException(nameof(number), "Must be greater than, or equal to, zero.");
if (number == 0) return content;
content = content.Children.FirstOrDefault();
@@ -1260,7 +1064,7 @@ namespace Umbraco.Web
public static T Parent<T>(this IPublishedContent content)
where T : class, IPublishedContent
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
return content.Parent as T;
}
@@ -1279,7 +1083,7 @@ namespace Umbraco.Web
/// </remarks>
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content)
{
if (content == null) throw new ArgumentNullException("content");
if (content == null) throw new ArgumentNullException(nameof(content));
return content.Children;
}
@@ -1328,24 +1132,26 @@ namespace Umbraco.Web
return content.Children<T>().FirstOrDefault();
}
/// <summary>
/// Gets the children of the content in a DataTable.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="contentTypeAliasFilter">An optional content type alias.</param>
/// <returns>The children of the content.</returns>
public static DataTable ChildrenAsTable(this IPublishedContent content, string contentTypeAliasFilter = "")
{
return GenerateDataTable(content, contentTypeAliasFilter);
}
/// <summary>
/// <summary>
/// Gets the children of the content in a DataTable.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="services">A service context.</param>
/// <param name="contentTypeAliasFilter">An optional content type alias.</param>
/// <returns>The children of the content.</returns>
private static DataTable GenerateDataTable(IPublishedContent content, string contentTypeAliasFilter = "")
public static DataTable ChildrenAsTable(this IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "")
{
return GenerateDataTable(content, services, contentTypeAliasFilter);
}
/// <summary>
/// Gets the children of the content in a DataTable.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="services">A service context.</param>
/// <param name="contentTypeAliasFilter">An optional content type alias.</param>
/// <returns>The children of the content.</returns>
private static DataTable GenerateDataTable(IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "")
{
var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace()
? content.Children.Any()
@@ -1360,7 +1166,7 @@ namespace Umbraco.Web
//pass in the alias of the first child node since this is the node type we're rendering headers for
firstNode.DocumentTypeAlias,
//pass in the callback to extract the Dictionary<string, string> of all defined aliases to their names
alias => GetPropertyAliasesAndNames(alias),
alias => GetPropertyAliasesAndNames(services, alias),
//pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place.
() =>
{
@@ -1404,72 +1210,45 @@ namespace Umbraco.Web
#endregion
#region OfTypes
// the .OfType<T>() filter is nice when there's only one type
// this is to support filtering with multiple types
public static IEnumerable<IPublishedContent> OfTypes(this IEnumerable<IPublishedContent> contents, params Type[] types)
{
return contents.Where(x => types.Contains(x.GetType()));
}
public static IEnumerable<IPublishedContent> OfTypes(this IEnumerable<IPublishedContent> contents, params string[] types)
{
types = types.Select(x => x.ToLowerInvariant()).ToArray();
return contents.Where(x => types.Contains(x.DocumentTypeAlias.ToLowerInvariant()));
}
public static T OfType<T>(this IPublishedContent content)
where T : class, IPublishedContent
{
return content as T;
}
#endregion
#region PropertyAliasesAndNames
private static Func<string, Dictionary<string, string>> _getPropertyAliasesAndNames;
private static Func<ServiceContext, string, Dictionary<string, string>> _getPropertyAliasesAndNames;
/// <summary>
/// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type
/// </summary>
internal static Func<string, Dictionary<string, string>> GetPropertyAliasesAndNames
internal static Func<ServiceContext, string, Dictionary<string, string>> GetPropertyAliasesAndNames
{
get
{
return _getPropertyAliasesAndNames ?? (_getPropertyAliasesAndNames = alias =>
{
var userFields = GetAliasesAndNames(alias);
//ensure the standard fields are there
var allFields = new Dictionary<string, string>
{
{"Id", "Id"},
{"NodeName", "NodeName"},
{"NodeTypeAlias", "NodeTypeAlias"},
{"CreateDate", "CreateDate"},
{"UpdateDate", "UpdateDate"},
{"CreatorName", "CreatorName"},
{"WriterName", "WriterName"},
{"Url", "Url"}
};
foreach (var f in userFields.Where(f => allFields.ContainsKey(f.Key) == false))
{
allFields.Add(f.Key, f.Value);
}
return allFields;
});
}
get { return _getPropertyAliasesAndNames ?? GetAliasesAndNames; }
set { _getPropertyAliasesAndNames = value; }
}
// fixme - temp, issue w/singleton, but removes dep on legacy
// should it work for media/members too?
private static Dictionary<string, string> GetAliasesAndNames(string alias)
private static Dictionary<string, string> GetAliasesAndNames(ServiceContext services, string alias)
{
var type = ApplicationContext.Current.Services.ContentTypeService.Get(alias);
return GetAliasesAndNames(type);
var type = services.ContentTypeService.Get(alias)
?? services.MediaTypeService.Get(alias)
?? (IContentTypeBase)services.MemberTypeService.Get(alias);
var fields = GetAliasesAndNames(type);
// ensure the standard fields are there
var stdFields = new Dictionary<string, string>
{
{"Id", "Id"},
{"NodeName", "NodeName"},
{"NodeTypeAlias", "NodeTypeAlias"},
{"CreateDate", "CreateDate"},
{"UpdateDate", "UpdateDate"},
{"CreatorName", "CreatorName"},
{"WriterName", "WriterName"},
{"Url", "Url"}
};
foreach (var field in stdFields.Where(x => fields.ContainsKey(x.Key) == false))
{
fields[field.Key] = field.Value;
}
return fields;
}
private static Dictionary<string, string> GetAliasesAndNames(IContentTypeBase contentType)
@@ -0,0 +1,212 @@
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web
{
/// <summary>
/// Provides extension methods for <c>IPublishedItem</c>.
/// </summary>
public static class PublishedFragmentExtensions
{
#region IsComposedOf
/// <summary>
/// Gets a value indicating whether the content is of a content type composed of the given alias
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The content type alias.</param>
/// <returns>A value indicating whether the content is of a content type composed of a content type identified by the alias.</returns>
public static bool IsComposedOf(this IPublishedFragment content, string alias)
{
return content.ContentType.CompositionAliases.Contains(alias);
}
#endregion
#region HasProperty
/// <summary>
/// Gets a value indicating whether the content has a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has the property identified by the alias.</returns>
/// <remarks>The content may have a property, and that property may not have a value.</remarks>
public static bool HasProperty(this IPublishedFragment content, string alias)
{
return content.ContentType.GetPropertyType(alias) != null;
}
#endregion
#region HasValue
/// <summary>
/// Gets a value indicating whether the content has a value for a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has a value for the property identified by the alias.</returns>
/// <remarks>Returns true if <c>GetProperty(alias)</c> is not <c>null</c> and <c>GetProperty(alias).HasValue</c> is <c>true</c>.</remarks>
public static bool HasValue(this IPublishedFragment content, string alias)
{
var prop = content.GetProperty(alias);
return prop != null && prop.HasValue;
}
/// <summary>
/// Returns one of two strings depending on whether the content has a value for a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="valueIfTrue">The value to return if the content has a value for the property.</param>
/// <param name="valueIfFalse">The value to return if the content has no value for the property.</param>
/// <returns>Either <paramref name="valueIfTrue"/> or <paramref name="valueIfFalse"/> depending on whether the content
/// has a value for the property identified by the alias.</returns>
public static IHtmlString HasValue(this IPublishedFragment content, string alias,
string valueIfTrue, string valueIfFalse = null)
{
return content.HasValue(alias)
? new HtmlString(valueIfTrue)
: new HtmlString(valueIfFalse ?? string.Empty);
}
#endregion
#region GetPropertyValue
/// <summary>
/// Gets the value of a content's property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>The value of the content's property identified by the alias.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <c>null</c>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias)
{
var property = content.GetProperty(alias);
return property?.Value;
}
/// <summary>
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias, string defaultValue)
{
var property = content.GetProperty(alias);
return property == null || property.HasValue == false ? defaultValue : property.Value;
}
/// <summary>
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object GetPropertyValue(this IPublishedFragment content, string alias, object defaultValue)
{
var property = content.GetProperty(alias);
return property == null || property.HasValue == false ? defaultValue : property.Value;
}
#endregion
#region GetPropertyValue<T>
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type.
/// </summary>
/// <typeparam name="T">The target property type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <c>default(T)</c>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static T GetPropertyValue<T>(this IPublishedFragment content, string alias)
{
return content.GetPropertyValue(alias, false, default(T));
}
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value.
/// </summary>
/// <typeparam name="T">The target property type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static T GetPropertyValue<T>(this IPublishedFragment content, string alias, T defaultValue)
{
return content.GetPropertyValue(alias, true, defaultValue);
}
internal static T GetPropertyValue<T>(this IPublishedFragment content, string alias, bool withDefaultValue, T defaultValue)
{
var property = content.GetProperty(alias);
if (property == null) return defaultValue;
return property.GetValue(withDefaultValue, defaultValue);
}
#endregion
#region ToIndexedArray
public static IndexedArrayItem<TContent>[] ToIndexedArray<TContent>(this IEnumerable<TContent> source)
where TContent : class, IPublishedFragment
{
var set = source.Select((content, index) => new IndexedArrayItem<TContent>(content, index)).ToArray();
foreach (var setItem in set) setItem.TotalCount = set.Length;
return set;
}
#endregion
#region OfTypes
// the .OfType<T>() filter is nice when there's only one type
// this is to support filtering with multiple types
public static IEnumerable<T> OfTypes<T>(this IEnumerable<T> contents, params string[] types)
where T : IPublishedFragment
{
types = types.Select(x => x.ToLowerInvariant()).ToArray();
return contents.Where(x => types.Contains(x.ContentType.Alias.ToLowerInvariant()));
}
#endregion
}
}
+2 -1
View File
@@ -213,9 +213,9 @@
<Compile Include="PublishedCache\NuCache\PublishedFragmentProperty.cs" />
<Compile Include="PublishedCache\PublishedFragment.cs" />
<Compile Include="PublishedCache\PublishedFragmentProperty.cs" />
<Compile Include="PublishedCache\PublishedFragmentPropertyBase.cs" />
<Compile Include="Models\Trees\DisableUser.cs" />
<Compile Include="PropertyEditors\ValueConverters\ContentPickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\TempValueConverter.cs" />
<Compile Include="PublishedCache\FacadeServiceBase.cs" />
<Compile Include="PublishedCache\FacadeServiceResolver.cs" />
<Compile Include="PublishedCache\IDomainCache.cs" />
@@ -258,6 +258,7 @@
<Compile Include="PublishedCache\XmlPublishedCache\PublishedMemberCache.cs" />
<Compile Include="PublishedCache\XmlPublishedCache\XmlStore.cs" />
<Compile Include="PublishedCache\XmlPublishedCache\XmlStoreFilePersister.cs" />
<Compile Include="PublishedFragmentExtensions.cs" />
<Compile Include="Routing\Domain.cs" />
<Compile Include="SignalR\PreviewHub.cs" />
<Compile Include="Trees\RelationTypeTreeController.cs" />