Files
Umbraco-CMS/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs

358 lines
14 KiB
C#
Raw Normal View History

2016-05-27 14:26:28 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
namespace Umbraco.Web.PublishedCache.NuCache
{
internal class PublishedContent : PublishedContentWithKeyBase
{
2016-05-30 19:54:36 +02:00
private readonly IFacadeAccessor _facadeAccessor;
2016-05-27 14:26:28 +02:00
private readonly ContentNode _contentNode;
// ReSharper disable once InconsistentNaming
internal readonly ContentData _contentData; // internal for ContentNode cloning
private readonly string _urlName;
#region Constructors
2016-05-30 19:54:36 +02:00
public PublishedContent(ContentNode contentNode, ContentData contentData, IFacadeAccessor facadeAccessor)
2016-05-27 14:26:28 +02:00
{
_contentNode = contentNode;
_contentData = contentData;
2016-05-30 19:54:36 +02:00
_facadeAccessor = facadeAccessor;
2016-05-27 14:26:28 +02:00
_urlName = _contentData.Name.ToUrlSegment();
2016-05-30 19:54:36 +02:00
IsPreviewing = _contentData.Published == false;
PropertiesArray = CreateProperties(this, contentData.Properties);
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private string GetProfileNameById(int id)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
var cache = GetCurrentFacadeCache();
2016-05-27 14:26:28 +02:00
return cache == null
? GetProfileNameByIdNoCache(id)
: (string) cache.GetCacheItem(CacheKeys.ProfileName(id), () => GetProfileNameByIdNoCache(id));
}
private static string GetProfileNameByIdNoCache(int id)
{
#if DEBUG
var context = ApplicationContext.Current;
2016-05-30 19:54:36 +02:00
var servicesContext = context?.Services;
var userService = servicesContext?.UserService;
2016-05-27 14:26:28 +02:00
if (userService == null) return "[null]"; // for tests
#else
// we don't want each published content to hold a reference to the service
// so where should they get the service from really? from the source...
var userService = ApplicationContext.Current.Services.UserService;
#endif
var user = userService.GetProfileById(id);
2016-05-30 19:54:36 +02:00
return user?.Name;
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private IPublishedProperty[] CreateProperties(PublishedContent content, IDictionary<string, object> values)
2016-05-27 14:26:28 +02:00
{
return content._contentNode.ContentType
.PropertyTypes
.Select(propertyType =>
{
object value;
return values.TryGetValue(propertyType.PropertyTypeAlias, out value)
2016-05-30 19:54:36 +02:00
? new Property(propertyType, content, value, _facadeAccessor) as IPublishedProperty
: new Property(propertyType, content, _facadeAccessor) as IPublishedProperty;
2016-05-27 14:26:28 +02:00
})
.ToArray();
}
// (see ContentNode.CloneParent)
2016-05-30 19:54:36 +02:00
public PublishedContent(ContentNode contentNode, PublishedContent origin, IFacadeAccessor facadeAccessor)
2016-05-27 14:26:28 +02:00
{
_contentNode = contentNode;
2016-05-30 19:54:36 +02:00
_facadeAccessor = facadeAccessor;
2016-05-27 14:26:28 +02:00
_contentData = origin._contentData;
_urlName = origin._urlName;
2016-05-30 19:54:36 +02:00
IsPreviewing = origin.IsPreviewing;
2016-05-27 14:26:28 +02:00
// here is the main benefit: we do not re-create properties so if anything
// is cached locally, we share the cache - which is fine - if anything depends
// on the tree structure, it should not be cached locally to begin with
2016-05-30 19:54:36 +02:00
PropertiesArray = origin.PropertiesArray;
2016-05-27 14:26:28 +02:00
}
// clone for previewing as draft a published content that is published and has no draft
2016-05-30 19:54:36 +02:00
private PublishedContent(PublishedContent origin, IFacadeAccessor facadeAccessor)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
_facadeAccessor = facadeAccessor;
2016-05-27 14:26:28 +02:00
_contentNode = origin._contentNode;
_contentData = origin._contentData;
_urlName = origin._urlName;
2016-05-30 19:54:36 +02:00
IsPreviewing = true;
2016-05-27 14:26:28 +02:00
// clone properties so _isPreviewing is true
2016-05-30 19:54:36 +02:00
PropertiesArray = origin.PropertiesArray.Select(x => (IPublishedProperty) new Property((Property) x)).ToArray();
2016-05-27 14:26:28 +02:00
}
#endregion
#region Get Content/Media for Parent/Children
// this is for tests purposes
// args are: current facade (may be null), previewing, content id - returns: content
private static Func<IFacade, bool, int, IPublishedContent> _getContentByIdFunc =
(facade, previewing, id) => facade.ContentCache.GetById(previewing, id);
private static Func<IFacade, bool, int, IPublishedContent> _getMediaByIdFunc =
(facade, previewing, id) => facade.MediaCache.GetById(previewing, id);
internal static Func<IFacade, bool, int, IPublishedContent> GetContentByIdFunc
{
get { return _getContentByIdFunc; }
set
{
_getContentByIdFunc = value;
}
}
internal static Func<IFacade, bool, int, IPublishedContent> GetMediaByIdFunc
{
get { return _getMediaByIdFunc; }
set
{
_getMediaByIdFunc = value;
}
}
2016-05-30 19:54:36 +02:00
private IPublishedContent GetContentById(bool previewing, int id)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
return _getContentByIdFunc(_facadeAccessor.Facade, previewing, id);
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private IEnumerable<IPublishedContent> GetContentByIds(bool previewing, IEnumerable<int> ids)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
var facade = _facadeAccessor.Facade;
2016-05-27 14:26:28 +02:00
// beware! the loop below CANNOT be converted to query such as:
//return ids.Select(x => _getContentByIdFunc(facade, previewing, x)).Where(x => x != null);
// because it would capture the facade and cause all sorts of issues
//
// we WANT to get the actual current facade each time we run
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var id in ids)
{
var content = _getContentByIdFunc(facade, previewing, id);
if (content != null) yield return content;
}
}
2016-05-30 19:54:36 +02:00
private IPublishedContent GetMediaById(bool previewing, int id)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
return _getMediaByIdFunc(_facadeAccessor.Facade, previewing, id);
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private IEnumerable<IPublishedContent> GetMediaByIds(bool previewing, IEnumerable<int> ids)
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
var facade = _facadeAccessor.Facade;
2016-05-27 14:26:28 +02:00
// see note above for content
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var id in ids)
{
var content = _getMediaByIdFunc(facade, previewing, id);
if (content != null) yield return content;
}
}
#endregion
#region IPublishedContent
2016-05-30 19:54:36 +02:00
public override int Id => _contentNode.Id;
public override Guid Key => _contentNode.Uid;
public override int DocumentTypeId => _contentNode.ContentType.Id;
public override string DocumentTypeAlias => _contentNode.ContentType.Alias;
public override PublishedItemType ItemType => _contentNode.ContentType.ItemType;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override string Name => _contentData.Name;
public override int Level => _contentNode.Level;
public override string Path => _contentNode.Path;
public override int SortOrder => _contentNode.SortOrder;
public override Guid Version => _contentData.Version;
public override int TemplateId => _contentData.TemplateId;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override string UrlName => _urlName;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override DateTime CreateDate => _contentNode.CreateDate;
public override DateTime UpdateDate => _contentData.VersionDate;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override int CreatorId => _contentNode.CreatorId;
public override string CreatorName => GetProfileNameById(_contentNode.CreatorId);
public override int WriterId => _contentData.WriterId;
public override string WriterName => GetProfileNameById(_contentData.WriterId);
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override bool IsDraft => _contentData.Published == false;
2016-05-27 14:26:28 +02:00
public override IPublishedContent Parent
{
get
{
// have to use the "current" cache because a PublishedContent can be shared
// amongst many snapshots and other content depend on the snapshots
switch (_contentNode.ContentType.ItemType)
{
case PublishedItemType.Content:
2016-05-30 19:54:36 +02:00
return GetContentById(IsPreviewing, _contentNode.ParentContentId);
2016-05-27 14:26:28 +02:00
case PublishedItemType.Media:
2016-05-30 19:54:36 +02:00
return GetMediaById(IsPreviewing, _contentNode.ParentContentId);
2016-05-27 14:26:28 +02:00
default:
throw new Exception("oops");
}
}
}
private string _childrenCacheKey;
2016-05-30 19:54:36 +02:00
private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing));
2016-05-27 14:26:28 +02:00
public override IEnumerable<IPublishedContent> Children
{
get
{
2016-05-30 19:54:36 +02:00
var cache = GetAppropriateCurrentFacadeCache();
2016-05-27 14:26:28 +02:00
if (cache == null || FacadeService.CachePublishedContentChildren == false)
return GetChildren();
// note: ToArray is important here, we want to cache the result, not the function!
return (IEnumerable<IPublishedContent>) cache.GetCacheItem(ChildrenCacheKey, () => GetChildren().ToArray());
}
}
private IEnumerable<IPublishedContent> GetChildren()
{
IEnumerable<IPublishedContent> c;
switch (_contentNode.ContentType.ItemType)
{
case PublishedItemType.Content:
2016-05-30 19:54:36 +02:00
c = GetContentByIds(IsPreviewing, _contentNode.ChildContentIds);
2016-05-27 14:26:28 +02:00
break;
case PublishedItemType.Media:
2016-05-30 19:54:36 +02:00
c = GetMediaByIds(IsPreviewing, _contentNode.ChildContentIds);
2016-05-27 14:26:28 +02:00
break;
default:
throw new Exception("oops");
}
return c.OrderBy(x => x.SortOrder);
// notes:
// _contentNode.ChildContentIds is an unordered int[]
// need needs to fetch & sort - do it only once, lazyily, though
// Q: perfs-wise, is it better than having the store managed an ordered list
}
2016-05-30 19:54:36 +02:00
public override ICollection<IPublishedProperty> Properties => PropertiesArray;
2016-05-27 14:26:28 +02:00
public override IPublishedProperty GetProperty(string alias)
{
var index = _contentNode.ContentType.GetPropertyIndex(alias);
2016-05-30 19:54:36 +02:00
var property = index < 0 ? null : PropertiesArray[index];
2016-05-27 14:26:28 +02:00
return property;
}
public override IPublishedProperty GetProperty(string alias, bool recurse)
{
var property = GetProperty(alias);
if (recurse == false) return property;
2016-05-30 19:54:36 +02:00
var cache = GetAppropriateCurrentFacadeCache();
2016-05-27 14:26:28 +02:00
if (cache == null)
return base.GetProperty(alias, true);
var key = ((Property) property).RecurseCacheKey;
return (Property) cache.GetCacheItem(key, () => base.GetProperty(alias, true));
}
2016-05-30 19:54:36 +02:00
public override PublishedContentType ContentType => _contentNode.ContentType;
#endregion
#region Caching
// beware what you use that one for - you don't want to cache its result
private ICacheProvider GetAppropriateCurrentFacadeCache()
{
var facade = (Facade) _facadeAccessor.Facade;
var cache = facade == null
? null
: ((IsPreviewing == false || FacadeService.FullCacheWhenPreviewing) && (ItemType != PublishedItemType.Member)
? facade.SnapshotCache
: facade.FacadeCache);
return cache;
}
private ICacheProvider GetCurrentFacadeCache()
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
var facade = (Facade) _facadeAccessor.Facade;
return facade?.FacadeCache;
2016-05-27 14:26:28 +02:00
}
#endregion
#region Internal
// used by navigable content
2016-05-30 19:54:36 +02:00
internal IPublishedProperty[] PropertiesArray { get; }
2016-05-27 14:26:28 +02:00
// used by navigable content
2016-05-30 19:54:36 +02:00
internal int ParentId => _contentNode.ParentContentId;
2016-05-27 14:26:28 +02:00
// used by navigable content
// includes all children, published or unpublished
// NavigableNavigator takes care of selecting those it wants
2016-05-30 19:54:36 +02:00
internal IList<int> ChildIds => _contentNode.ChildContentIds;
2016-05-27 14:26:28 +02:00
// used by Property
// gets a value indicating whether the content or media exists in
// a previewing context or not, ie whether its Parent, Children, and
// properties should refer to published, or draft content
2016-05-30 19:54:36 +02:00
internal bool IsPreviewing { get; }
2016-05-27 14:26:28 +02:00
private string _asPreviewingCacheKey;
2016-05-30 19:54:36 +02:00
private string AsPreviewingCacheKey => _asPreviewingCacheKey ?? (_asPreviewingCacheKey = CacheKeys.PublishedContentAsPreviewing(Key));
2016-05-27 14:26:28 +02:00
// used by ContentCache
internal IPublishedContent AsPreviewingModel()
{
2016-05-30 19:54:36 +02:00
if (IsPreviewing)
2016-05-27 14:26:28 +02:00
return this;
2016-05-30 19:54:36 +02:00
var cache = GetAppropriateCurrentFacadeCache();
if (cache == null) return new PublishedContent(this, _facadeAccessor).CreateModel();
return (IPublishedContent) cache.GetCacheItem(AsPreviewingCacheKey, () => new PublishedContent(this, _facadeAccessor).CreateModel());
2016-05-27 14:26:28 +02:00
}
// used by Navigable.Source,...
internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content)
{
PublishedContentWrapped wrapped;
while ((wrapped = content as PublishedContentWrapped) != null)
content = wrapped.Unwrap();
var inner = content as PublishedContent;
if (inner == null)
throw new InvalidOperationException("Innermost content is not PublishedContent.");
return inner;
}
#endregion
}
}