From 792ab8d088a873aaaf04c9421db92fa93fa48c16 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 3 Mar 2014 13:29:33 +0100 Subject: [PATCH] Introduce support for content fragments --- .../PublishedContentTestElements.cs | 6 + .../PublishedCache/IPublishedContentCache.cs | 48 +++++ .../PublishedContentCache.cs | 10 ++ .../XmlPublishedCache/PublishedFragment.cs | 169 ++++++++++++++++++ .../PublishedFragmentProperty.cs | 43 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 6 files changed, 278 insertions(+) create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 3f3fa0a926..be357c4c03 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -99,6 +99,12 @@ namespace Umbraco.Tests.PublishedContent { return _content.Count > 0; } + + public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + throw new NotImplementedException(); + } } class SolidPublishedContent : IPublishedContent diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs index a80aaa41b3..f020dc6097 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs @@ -33,5 +33,53 @@ namespace Umbraco.Web.PublishedCache /// The route. /// The value of overrides the context. string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId); + + /// + /// Creates a content fragment. + /// + /// The content type alias. + /// The content property raw values. + /// A value indicating whether the fragment is previewing. + /// A value indicating whether the fragment is managed by the cache. + /// The newly created content fragment. + // + // notes + // + // in XmlPublishedCache, IPublishedContent instances are not meant to survive longer + // that a request or else we cannot guarantee that the converted property values will + // be properly managed - because XmlPublishedProperty just stores the result of the + // conversion locally. + // + // in DrippingPublishedCache, IPublishedContent instances are meant to survive for as + // long as the content itself has not been modified, and the property respects the + // converter's indication ie whether the converted value should be cached at + // .Content - cache until the content changes + // .ContentCache - cache until any content changes + // .Request - cache for the current request + // + // a fragment can be either "detached" or "managed". + // detached: created from code, managed by code, converted property values are + // cached within the fragment itself for as long as the fragment lives + // managed: created from a property converter as part of a content, managed by + // the cache, converted property values can be cached... + // + // XmlPublishedCache: same as content properties, store the result of the + // conversion locally, neither content nor fragments should survive longer + // than a request + // DrippingPublishedCache: depends + // .Content: cache within the fragment + // .ContentCache, .Request: cache within the cache + // + // in the latter case, use a fragment-owned guid as the cache key. because we + // don't really have any other choice. this opens potential memory leaks: if the + // fragment is re-created on each request and has a property that caches its + // converted value at .ContentCache level then we'll flood that cache with data + // that's never removed (as long as no content is edited). + // + // so a requirement should be that any converter that creates fragment, should + // be marked .Content -- and nothing else + // + IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed); } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 82a5b6f85a..2fe9c13c21 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -460,5 +460,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } #endregion + + #region Fragments + + public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + return new PublishedFragment(contentTypeAlias, dataValues, isPreviewing, managed); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs new file mode 100644 index 0000000000..860f9f043f --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PublishedFragment : PublishedContentBase + { + private readonly PublishedContentType _contentType; + private readonly IPublishedProperty[] _properties; + + public PublishedFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + IsPreviewing = isPreviewing; + _contentType = PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias); + + // we don't care about managed because in both cases, XmlPublishedCache stores + // converted property values in the IPublishedContent, which is not meant to + // survive the request + + var dataValues2 = new Dictionary(); + foreach (var kvp in dataValues) + dataValues2[kvp.Key.ToLowerInvariant()] = kvp.Value; + + _properties = _contentType.PropertyTypes + .Select(x => + { + object dataValue; + return dataValues2.TryGetValue(x.PropertyTypeAlias.ToLowerInvariant(), out dataValue) + ? new PublishedFragmentProperty(x, this, dataValue) + : new PublishedFragmentProperty(x, this); + }) + .Cast() + .ToArray(); + } + + #region IPublishedContent + + public override int Id + { + get { throw new NotImplementedException(); } + } + + public override int DocumentTypeId + { + get { return _contentType.Id; } + } + + public override string DocumentTypeAlias + { + get { return _contentType.Alias; } + } + + public override PublishedItemType ItemType + { + get { return PublishedItemType.Content; } + } + + public override string Name + { + get { throw new NotImplementedException(); } + } + + public override int Level + { + get { throw new NotImplementedException(); } + } + + public override string Path + { + get { throw new NotImplementedException(); } + } + + public override int SortOrder + { + // note - could a published fragment have a sort order? + get { throw new NotImplementedException(); } + } + + public override Guid Version + { + get { throw new NotImplementedException(); } + } + + public override int TemplateId + { + get { throw new NotImplementedException(); } + } + + public override string UrlName + { + get { return string.Empty; } + } + + public override DateTime CreateDate + { + get { throw new NotImplementedException(); } + } + + public override DateTime UpdateDate + { + get { throw new NotImplementedException(); } + } + + public override int CreatorId + { + get { throw new NotImplementedException(); } + } + + public override string CreatorName + { + get { throw new NotImplementedException(); } + } + + public override int WriterId + { + get { throw new NotImplementedException(); } + } + + public override string WriterName + { + get { throw new NotImplementedException(); } + } + + public override bool IsDraft + { + get { throw new NotImplementedException(); } + } + + public override IPublishedContent Parent + { + get { throw new NotImplementedException(); } + } + + public override IEnumerable Children + { + get { throw new NotImplementedException(); } + } + + public override ICollection Properties + { + get { return _properties; } + } + + public override IPublishedProperty GetProperty(string alias) + { + return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + + public override PublishedContentType ContentType + { + get { return _contentType; } + } + + #endregion + + #region Internal + + // used by PublishedFragmentProperty + internal bool IsPreviewing { get; private set; } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs new file mode 100644 index 0000000000..7ae10aebdd --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs @@ -0,0 +1,43 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PublishedFragmentProperty : PublishedPropertyBase + { + private readonly object _dataValue; + private readonly PublishedFragment _content; + + private readonly Lazy _sourceValue; + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + + public PublishedFragmentProperty(PublishedPropertyType propertyType, PublishedFragment content) + : this(propertyType, content, null) + { } + + public PublishedFragmentProperty(PublishedPropertyType propertyType, PublishedFragment content, object dataValue) + : base(propertyType) + { + _dataValue = dataValue; + _content = content; + + _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_dataValue, _content.IsPreviewing)); + _objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _content.IsPreviewing)); + _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _content.IsPreviewing)); + } + + public override bool HasValue + { + get { return _dataValue != null && ((_dataValue is string) == false || string.IsNullOrWhiteSpace((string)_dataValue) == false); } + } + + public override object DataValue + { + get { return _dataValue; } + } + + public override object Value { get { return _objectValue.Value; } } + public override object XPathValue { get { return _xpathValue.Value; } } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f1a6f79d57..130a6d0fbf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -298,6 +298,8 @@ + +