diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs new file mode 100644 index 0000000000..57d1ee3492 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Subpage which acts as the strongly typed model for a Doc Type + /// with alias "Subpage" and "Subpage" as the allowed child type. + /// + /// Similar to the Textpage this model could also be generated, but it could also + /// act as a Code-First model by using the attributes shown on the various properties, + /// which decorate the model with information about the Document Type, its + /// Property Groups and Property Types. + /// + public class Subpage : TypedModelBase + { + public Subpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs new file mode 100644 index 0000000000..fff1ae9107 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Textpage which acts as the strongly typed model for a Doc Type + /// with alias "Textpage" and "Subpage" as the allowed child type. + /// + /// The basic properties are resolved by convention using the Resolve-Type-PropertyTypeAlias + /// convention available through the base class' protected Resolve-method and Property-delegate. + /// + /// The Textpage allows the use of Subpage and Textpage as child doc types, which are exposed as a + /// collection using the Children-Type-ContentTypeAlias convention available through the + /// base class' protected Children-method and ContentTypeAlias-delegate. + /// + /// + /// This code can easily be generated using simple conventions for the types and names + /// of the properties that this type of strongly typed model exposes. + /// + public class Textpage : TypedModelBase + { + public Textpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public string AuthorName { get { return Resolve(Property()); } } + + public DateTime Date { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Textpages + { + get + { + return Children(ContentTypeAlias()); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs deleted file mode 100644 index 47f2f817f1..0000000000 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Umbraco.Core.Models; - -namespace Umbraco.Tests.PublishedContent.StronglyTypedModels -{ - public class TextpageModel : TypedModelBase - { - public TextpageModel(IPublishedContent publishedContent) : base(publishedContent) - { - } - - public string Title { get { return Resolve(Property(), DefaultString); } } - - public string BodyText { get { return Resolve(Property(), DefaultString); } } - - public string AuthorName { get { return Resolve(Property()); } } - - public DateTime Date { get { return Resolve(Property(), DefaultDateTime); } } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index 4cc6e2ebf2..d606be3c5b 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -1,12 +1,34 @@ using System; +using System.Collections.Generic; +using System.Data.Entity.Design.PluralizationServices; +using System.Globalization; using System.Reflection; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { - public abstract class TypedModelBase + /// + /// Represents the abstract base class for a 'TypedModel', which basically wraps IPublishedContent + /// underneath a strongly typed model like "Textpage" and "Subpage". + /// Because IPublishedContent is used under the hood there is no need for additional mapping, so the + /// only added cost should be the creation of the objects, which the IPublishedContent instance is + /// passed into. + /// + /// This base class exposes a simple way to write property getters by convention without + /// using the string alias of a PropertyType (this is resolved by the use of the Property delegate). + /// + /// This base class also exposes query options like Parent, Children, Ancestors and Descendants, + /// which can be used for collections of strongly typed child models/objects. These types of collections + /// typically corresponds to 'allowed child content types' on a Doc Type (at different levels). + /// + /// The IPublishedContent properties are also exposed through this base class, but only + /// by casting the typed model to IPublishedContent, so the properties doesn't show up by default: + /// ie. ((IPublishedContent)textpage).Url + /// + public abstract class TypedModelBase : IPublishedContent { private readonly IPublishedContent _publishedContent; @@ -15,56 +37,168 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels _publishedContent = publishedContent; } - public readonly Func Property = MethodBase.GetCurrentMethod; - public static string DefaultString = default(string); - public static int DefaultInteger = default(int); - public static bool DefaultBool = default(bool); - public static DateTime DefaultDateTime = default(DateTime); + protected readonly Func Property = MethodBase.GetCurrentMethod; + protected readonly Func ContentTypeAlias = MethodBase.GetCurrentMethod; - public T Resolve(MethodBase methodBase) + #region Properties + + protected T Resolve(MethodBase methodBase) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias); + } + + protected T Resolve(string propertyTypeAlias) + { return _publishedContent.GetPropertyValue(propertyTypeAlias); } - public T Resolve(MethodBase methodBase, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); } - public T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, recursive, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, bool recursive, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); } + #endregion - public string ResolveString(MethodBase methodBase) + #region Querying + protected T Parent() where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + return (T) constructorInfo.Invoke(new object[] {_publishedContent.Parent}); } - public int ResolveInt(MethodBase methodBase) + protected IEnumerable Children(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Children(docTypeAlias); } - public bool ResolveBool(MethodBase methodBase) + protected IEnumerable Children(string docTypeAlias) where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if(constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } - public DateTime ResolveDate(MethodBase methodBase) + protected IEnumerable Ancestors(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Ancestors(docTypeAlias); } + + protected IEnumerable Ancestors(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + + protected IEnumerable Descendants(MethodBase methodBase) where T : TypedModelBase + { + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Descendants(docTypeAlias); + } + + protected IEnumerable Descendants(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + #endregion + + #region IPublishedContent + int IPublishedContent.Id { get { return _publishedContent.Id; } } + int IPublishedContent.TemplateId { get { return _publishedContent.TemplateId; } } + int IPublishedContent.SortOrder { get { return _publishedContent.SortOrder; } } + string IPublishedContent.Name { get { return _publishedContent.Name; } } + string IPublishedContent.UrlName { get { return _publishedContent.UrlName; } } + string IPublishedContent.DocumentTypeAlias { get { return _publishedContent.DocumentTypeAlias; } } + int IPublishedContent.DocumentTypeId { get { return _publishedContent.DocumentTypeId; } } + string IPublishedContent.WriterName { get { return _publishedContent.WriterName; } } + string IPublishedContent.CreatorName { get { return _publishedContent.CreatorName; } } + int IPublishedContent.WriterId { get { return _publishedContent.WriterId; } } + int IPublishedContent.CreatorId { get { return _publishedContent.CreatorId; } } + string IPublishedContent.Path { get { return _publishedContent.Path; } } + DateTime IPublishedContent.CreateDate { get { return _publishedContent.CreateDate; } } + DateTime IPublishedContent.UpdateDate { get { return _publishedContent.UpdateDate; } } + Guid IPublishedContent.Version { get { return _publishedContent.Version; } } + int IPublishedContent.Level { get { return _publishedContent.Level; } } + string IPublishedContent.Url { get { return _publishedContent.Url; } } + PublishedItemType IPublishedContent.ItemType { get { return _publishedContent.ItemType; } } + IPublishedContent IPublishedContent.Parent { get { return _publishedContent.Parent; } } + IEnumerable IPublishedContent.Children { get { return _publishedContent.Children; } } + + ICollection IPublishedContent.Properties { get { return _publishedContent.Properties; } } + + object IPublishedContent.this[string propertyAlias] { get { return _publishedContent[propertyAlias]; } } + + IPublishedContentProperty IPublishedContent.GetProperty(string alias) + { + return _publishedContent.GetProperty(alias); + } + #endregion } + /// + /// Extension methods for MethodBase, which are used to clean the name of the calling method "get_BodyText" + /// to "BodyText" and then make it camel case according to the UmbracoAlias convention "bodyText". + /// There is also a string extension for making plural words singular, which is used when going from + /// something like "Subpages" to "Subpage" for Children/Ancestors/Descendants Doc Type aliases. + /// public static class TypeExtensions { + public static string CleanCallingMethodName(this MethodBase methodBase) + { + return methodBase.Name.Replace("get_", ""); + } + public static string ToUmbracoAlias(this MethodBase methodBase) { - return methodBase.Name.Replace("get_", "").ToUmbracoAlias(); + return methodBase.CleanCallingMethodName().ToUmbracoAlias(); + } + + public static string ToSingular(this string pluralWord) + { + var service = PluralizationService.CreateService(new CultureInfo("en-US")); + if (service.IsPlural(pluralWord)) + return service.Singularize(pluralWord); + + return pluralWord; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs index 6e27886b83..ae0a3c062e 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -3,6 +3,12 @@ using Umbraco.Web.Mvc; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { + /// + /// Represents a basic extension of the UmbracoTemplatePage, which allows you to specify + /// the type of a strongly typed model that inherits from TypedModelBase. + /// The model is exposed as TypedModel. + /// + /// Type of the model to create/expose public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase { protected override void InitializePage() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e9ac0dbc8f..032fcb8bfb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -82,6 +82,7 @@ + True ..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll @@ -194,7 +195,8 @@ - + +