Committing some POC code in the test project for strongly typed models/queries

This commit is contained in:
Morten Christensen
2013-09-11 10:16:49 +02:00
parent c5c44cec37
commit 8d510fd512
6 changed files with 262 additions and 39 deletions

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
{
/// <summary>
/// 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.
/// </summary>
public class Subpage : TypedModelBase
{
public Subpage(IPublishedContent publishedContent) : base(publishedContent)
{
}
public string Title { get { return Resolve<string>(Property()); } }
public string BodyText { get { return Resolve<string>(Property()); } }
public Textpage Parent
{
get
{
return Parent<Textpage>();
}
}
public IEnumerable<Subpage> Subpages
{
get
{
return Children<Subpage>(ContentTypeAlias());
}
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Textpage : TypedModelBase
{
public Textpage(IPublishedContent publishedContent) : base(publishedContent)
{
}
public string Title { get { return Resolve<string>(Property()); } }
public string BodyText { get { return Resolve<string>(Property()); } }
public string AuthorName { get { return Resolve<string>(Property()); } }
public DateTime Date { get { return Resolve<DateTime>(Property()); } }
public Textpage Parent
{
get
{
return Parent<Textpage>();
}
}
public IEnumerable<Textpage> Textpages
{
get
{
return Children<Textpage>(ContentTypeAlias());
}
}
public IEnumerable<Subpage> Subpages
{
get
{
return Children<Subpage>(ContentTypeAlias());
}
}
}
}

View File

@@ -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<string>(Property()); } }
public DateTime Date { get { return Resolve(Property(), DefaultDateTime); } }
}
}

View File

@@ -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
/// <summary>
/// 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
/// </summary>
public abstract class TypedModelBase : IPublishedContent
{
private readonly IPublishedContent _publishedContent;
@@ -15,56 +37,168 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
_publishedContent = publishedContent;
}
public readonly Func<MethodBase> 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<MethodBase> Property = MethodBase.GetCurrentMethod;
protected readonly Func<MethodBase> ContentTypeAlias = MethodBase.GetCurrentMethod;
public T Resolve<T>(MethodBase methodBase)
#region Properties
protected T Resolve<T>(MethodBase methodBase)
{
var propertyTypeAlias = methodBase.ToUmbracoAlias();
return Resolve<T>(propertyTypeAlias);
}
protected T Resolve<T>(string propertyTypeAlias)
{
return _publishedContent.GetPropertyValue<T>(propertyTypeAlias);
}
public T Resolve<T>(MethodBase methodBase, T ifCannotConvert)
protected T Resolve<T>(MethodBase methodBase, T ifCannotConvert)
{
var propertyTypeAlias = methodBase.ToUmbracoAlias();
return Resolve<T>(propertyTypeAlias, ifCannotConvert);
}
protected T Resolve<T>(string propertyTypeAlias, T ifCannotConvert)
{
return _publishedContent.GetPropertyValue<T>(propertyTypeAlias, false, ifCannotConvert);
}
public T Resolve<T>(MethodBase methodBase, bool recursive, T ifCannotConvert)
protected T Resolve<T>(MethodBase methodBase, bool recursive, T ifCannotConvert)
{
var propertyTypeAlias = methodBase.ToUmbracoAlias();
return Resolve<T>(propertyTypeAlias, recursive, ifCannotConvert);
}
protected T Resolve<T>(string propertyTypeAlias, bool recursive, T ifCannotConvert)
{
return _publishedContent.GetPropertyValue<T>(propertyTypeAlias, recursive, ifCannotConvert);
}
#endregion
public string ResolveString(MethodBase methodBase)
#region Querying
protected T Parent<T>() where T : TypedModelBase
{
return Resolve<string>(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<T> Children<T>(MethodBase methodBase) where T : TypedModelBase
{
return Resolve<int>(methodBase);
var docTypeAlias = methodBase.CleanCallingMethodName();
return Children<T>(docTypeAlias);
}
public bool ResolveBool(MethodBase methodBase)
protected IEnumerable<T> Children<T>(string docTypeAlias) where T : TypedModelBase
{
return Resolve<bool>(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<T> Ancestors<T>(MethodBase methodBase) where T : TypedModelBase
{
return Resolve<DateTime>(methodBase);
var docTypeAlias = methodBase.CleanCallingMethodName();
return Ancestors<T>(docTypeAlias);
}
protected IEnumerable<T> Ancestors<T>(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<T> Descendants<T>(MethodBase methodBase) where T : TypedModelBase
{
var docTypeAlias = methodBase.CleanCallingMethodName();
return Descendants<T>(docTypeAlias);
}
protected IEnumerable<T> Descendants<T>(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> IPublishedContent.Children { get { return _publishedContent.Children; } }
ICollection<IPublishedContentProperty> 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
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -3,6 +3,12 @@ using Umbraco.Web.Mvc;
namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Type of the model to create/expose</typeparam>
public abstract class UmbracoTemplatePage<T> : UmbracoTemplatePage where T : TypedModelBase
{
protected override void InitializePage()

View File

@@ -82,6 +82,7 @@
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity.Design" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll</HintPath>
@@ -194,7 +195,8 @@
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserTypeRepositoryTest.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\CallingMethodTests.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\TextpageModel.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\Subpage.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\Textpage.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
<Compile Include="Services\LocalizationServiceTests.cs" />