Refactor - IPublishedContent, converters, fragments (cont)
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user