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 @@
-
+
+