Skip to content

Instantly share code, notes, and snippets.

@jbreuer
Created May 18, 2016 14:59
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jbreuer/dde3605035179c34b7287850c45cb8c9 to your computer and use it in GitHub Desktop.
Save jbreuer/dde3605035179c34b7287850c45cb8c9 to your computer and use it in GitHub Desktop.
An extension method to convert an IContent to an IPublishedContent.
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ContentExtensions.cs" company="Colours B.V.">
// © Colours B.V. 2015
// </copyright>
// <summary>
// The content extensions.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Project.Web.Core.Extensions
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Xml.Serialization;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Strings;
using Umbraco.Web.Models;
/// <summary>
/// The conten extensions.
/// </summary>
public static class ContentExtensions
{
/// <summary>
/// Convert an IContent to an IPublishedContent.
/// </summary>
/// <param name="content">
/// The content.
/// </param>
/// <param name="isPreview">
/// The is preview.
/// </param>
/// <returns>
/// The <see cref="IPublishedContent"/>.
/// </returns>
public static IPublishedContent ToPublishedContent(this IContent content, bool isPreview = false)
{
return new PublishedContent(content, isPreview);
}
}
/// <summary>
/// The published content.
/// </summary>
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Reviewed. Suppression is OK here.")]
public class PublishedContent : PublishedContentWithKeyBase
{
private readonly PublishedContentType contentType;
private readonly IContent inner;
private readonly bool isPreviewing;
private readonly Lazy<string> lazyCreatorName;
private readonly Lazy<string> lazyUrlName;
private readonly Lazy<string> lazyWriterName;
private readonly IPublishedProperty[] properties;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContent"/> class.
/// </summary>
/// <param name="inner">
/// The inner.
/// </param>
/// <param name="isPreviewing">
/// The is previewing.
/// </param>
public PublishedContent(IContent inner, bool isPreviewing)
{
if (inner == null)
{
throw new NullReferenceException("inner");
}
this.inner = inner;
this.isPreviewing = isPreviewing;
this.lazyUrlName = new Lazy<string>(() => this.inner.GetUrlSegment().ToLower());
#pragma warning disable 618
this.lazyCreatorName = new Lazy<string>(() => this.inner.GetCreatorProfile().Name);
#pragma warning restore 618
#pragma warning disable 618
this.lazyWriterName = new Lazy<string>(() => this.inner.GetWriterProfile().Name);
#pragma warning restore 618
this.contentType = PublishedContentType.Get(PublishedItemType.Content, this.inner.ContentType.Alias);
this.properties =
MapProperties(
this.contentType.PropertyTypes,
this.inner.Properties,
(t, v) => new PublishedProperty(t, v, this.isPreviewing)).ToArray();
}
/// <summary>
/// Gets the id.
/// </summary>
public override int Id
{
get
{
return this.inner.Id;
}
}
/// <summary>
/// Gets the key.
/// </summary>
public override Guid Key
{
get
{
return this.inner.Key;
}
}
/// <summary>
/// Gets the document type id.
/// </summary>
public override int DocumentTypeId
{
get
{
return this.inner.ContentTypeId;
}
}
/// <summary>
/// Gets the document type alias.
/// </summary>
public override string DocumentTypeAlias
{
get
{
return this.inner.ContentType.Alias;
}
}
/// <summary>
/// Gets the item type.
/// </summary>
public override PublishedItemType ItemType
{
get
{
return PublishedItemType.Content;
}
}
/// <summary>
/// Gets the name.
/// </summary>
public override string Name
{
get
{
return this.inner.Name;
}
}
/// <summary>
/// Gets the level.
/// </summary>
public override int Level
{
get
{
return this.inner.Level;
}
}
/// <summary>
/// Gets the path.
/// </summary>
public override string Path
{
get
{
return this.inner.Path;
}
}
/// <summary>
/// Gets the sort order.
/// </summary>
public override int SortOrder
{
get
{
return this.inner.SortOrder;
}
}
/// <summary>
/// Gets the version.
/// </summary>
public override Guid Version
{
get
{
return this.inner.Version;
}
}
/// <summary>
/// Gets the template id.
/// </summary>
public override int TemplateId
{
get
{
return this.inner.Template == null ? 0 : this.inner.Template.Id;
}
}
/// <summary>
/// Gets the url name.
/// </summary>
public override string UrlName
{
get
{
return this.lazyUrlName.Value;
}
}
/// <summary>
/// Gets the create date.
/// </summary>
public override DateTime CreateDate
{
get
{
return this.inner.CreateDate;
}
}
/// <summary>
/// Gets the update date.
/// </summary>
public override DateTime UpdateDate
{
get
{
return this.inner.UpdateDate;
}
}
/// <summary>
/// Gets the creator id.
/// </summary>
public override int CreatorId
{
get
{
return this.inner.CreatorId;
}
}
/// <summary>
/// Gets the creator name.
/// </summary>
public override string CreatorName
{
get
{
return this.lazyCreatorName.Value;
}
}
/// <summary>
/// Gets the writer id.
/// </summary>
public override int WriterId
{
get
{
return this.inner.WriterId;
}
}
/// <summary>
/// Gets the writer name.
/// </summary>
public override string WriterName
{
get
{
return this.lazyWriterName.Value;
}
}
/// <summary>
/// Gets a value indicating whether is draft.
/// </summary>
public override bool IsDraft
{
get
{
return this.inner.Published == false;
}
}
/// <summary>
/// Gets the parent.
/// </summary>
public override IPublishedContent Parent
{
get
{
var parent = this.inner.Parent();
return parent.ToPublishedContent(this.isPreviewing);
}
}
/// <summary>
/// Gets the children.
/// </summary>
public override IEnumerable<IPublishedContent> Children
{
get
{
var children = this.inner.Children().ToList();
return
children.Select(x => x.ToPublishedContent(this.isPreviewing))
.Where(x => x != null)
.OrderBy(x => x.SortOrder);
}
}
/// <summary>
/// Gets the properties.
/// </summary>
public override ICollection<IPublishedProperty> Properties
{
get
{
return this.properties;
}
}
/// <summary>
/// Gets the content type.
/// </summary>
public override PublishedContentType ContentType
{
get
{
return this.contentType;
}
}
/// <summary>
/// The get property.
/// </summary>
/// <param name="alias">
/// The alias.
/// </param>
/// <returns>
/// The <see cref="IPublishedProperty"/>.
/// </returns>
public override IPublishedProperty GetProperty(string alias)
{
return this.properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias));
}
/// <summary>
/// The map properties.
/// </summary>
/// <param name="propertyTypes">
/// The property types.
/// </param>
/// <param name="properties">
/// The properties.
/// </param>
/// <param name="map">
/// The map.
/// </param>
/// <returns>
/// The <see cref="IEnumerable{T}"/>.
/// </returns>
internal static IEnumerable<IPublishedProperty> MapProperties(
IEnumerable<PublishedPropertyType> propertyTypes,
IEnumerable<Property> properties,
Func<PublishedPropertyType, object, IPublishedProperty> map)
{
var propertyEditorResolver = PropertyEditorResolver.Current;
var dataTypeService = ApplicationContext.Current.Services.DataTypeService;
return propertyTypes.Select(
x =>
{
var p = properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias);
var v = p == null || p.Value == null ? null : p.Value;
if (v != null)
{
var e = propertyEditorResolver.GetByAlias(x.PropertyEditorAlias);
// We are converting to string, even for database values which are integer or
// DateTime, which is not optimum. Doing differently would require that we have a way to tell
// whether the conversion to XML string changes something or not... which we don't, and we
// don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway.
// Don't think about improving the situation here: this is a corner case and the real
// thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String.
// Use ConvertDbToString to keep it simple, although everywhere we use ConvertDbToXml and
// nothing ensures that the two methods are consistent.
if (e != null)
{
v = e.ValueEditor.ConvertDbToString(p, p.PropertyType, dataTypeService);
}
}
return map(x, v);
});
}
}
internal static class ContentBaseExtensions
{
/// <summary>
/// Gets the url segment providers.
/// </summary>
/// <remarks>This is so that unit tests that do not initialize the resolver do not
/// fail and fall back to defaults. When running the whole Umbraco, CoreBootManager
/// does initialise the resolver.</remarks>
private static IEnumerable<IUrlSegmentProvider> UrlSegmentProviders
{
get
{
return UrlSegmentProviderResolver.HasCurrent
? UrlSegmentProviderResolver.Current.Providers
: new IUrlSegmentProvider[] { new DefaultUrlSegmentProvider() };
}
}
/// <summary>
/// Gets the default url segment for a specified content.
/// </summary>
/// <param name="content">
/// The content.
/// </param>
/// <returns>
/// The url segment.
/// </returns>
public static string GetUrlSegment(this IContentBase content)
{
var url = UrlSegmentProviders.Select(p => p.GetUrlSegment(content)).First(u => u != null);
url = url ?? new DefaultUrlSegmentProvider().GetUrlSegment(content); // be safe
return url;
}
/// <summary>
/// Gets the url segment for a specified content and culture.
/// </summary>
/// <param name="content">
/// The content.
/// </param>
/// <param name="culture">
/// The culture.
/// </param>
/// <returns>
/// The url segment.
/// </returns>
public static string GetUrlSegment(this IContentBase content, CultureInfo culture)
{
var url = UrlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).First(u => u != null);
url = url ?? new DefaultUrlSegmentProvider().GetUrlSegment(content, culture); // be safe
return url;
}
}
[Serializable]
[XmlType(Namespace = "http://umbraco.org/webservices/")]
internal class PublishedProperty : PublishedPropertyBase
{
private readonly object dataValue;
private readonly bool isPreviewing;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedProperty"/> class.
/// </summary>
/// <param name="propertyType">
/// The property type.
/// </param>
/// <param name="dataValue">
/// The data value.
/// </param>
/// <param name="isPreviewing">
/// The is previewing.
/// </param>
public PublishedProperty(PublishedPropertyType propertyType, object dataValue, bool isPreviewing)
: base(propertyType)
{
this.dataValue = dataValue;
this.isPreviewing = isPreviewing;
}
/// <summary>
/// Gets a value indicating whether has value.
/// </summary>
public override bool HasValue
{
get
{
return this.dataValue != null
&& ((this.dataValue is string) == false
|| string.IsNullOrWhiteSpace((string)this.dataValue) == false);
}
}
/// <summary>
/// Gets the data value.
/// </summary>
public override object DataValue
{
get
{
return this.dataValue;
}
}
/// <summary>
/// Gets the value.
/// </summary>
public override object Value
{
get
{
var source = this.PropertyType.ConvertDataToSource(this.dataValue, this.isPreviewing);
return this.PropertyType.ConvertSourceToObject(source, this.isPreviewing);
}
}
/// <summary>
/// Gets the x path value.
/// </summary>
public override object XPathValue
{
get
{
var source = this.PropertyType.ConvertDataToSource(this.dataValue, this.isPreviewing);
return this.PropertyType.ConvertSourceToXPath(source, this.isPreviewing);
}
}
}
internal abstract class PublishedPropertyBase : IPublishedProperty
{
/// <summary>
/// The property type.
/// </summary>
public readonly PublishedPropertyType PropertyType;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedPropertyBase"/> class.
/// </summary>
/// <param name="propertyType">
/// The property type.
/// </param>
protected PublishedPropertyBase(PublishedPropertyType propertyType)
{
if (propertyType == null)
{
throw new ArgumentNullException("propertyType");
}
this.PropertyType = propertyType;
}
/// <summary>
/// Gets the property type alias.
/// </summary>
public string PropertyTypeAlias
{
get
{
return this.PropertyType.PropertyTypeAlias;
}
}
/// <summary>
/// Gets a value indicating whether has value.
/// </summary>
public abstract bool HasValue { get; }
/// <summary>
/// Gets the data value.
/// </summary>
public abstract object DataValue { get; }
/// <summary>
/// Gets the value.
/// </summary>
public abstract object Value { get; }
/// <summary>
/// Gets the x path value.
/// </summary>
public abstract object XPathValue { get; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment