Wednesday, July 13, 2011

EPiServer, OpenWaves, and PageDataAdapters

This is a first post after a long break. Considering my lack of discipline and time I’m pretty sure this will not turn into regular blogging but anyways.
Some context first. It’s been 4 years since I started working for Making Waves. One of the thinks we do is implementing EPiServer CMS. If you have never heard of it, don’t read further – this may bore you.
Still with me? Good. From now on I’ll assume you know what EPiServer is and that you are a developer.

Open Waves

Making Waves does a lot of projects. As you can imagine a lot of code written for one project very often can be reused in another. For some time, we have been trying to extract anything that seemed reusable into a library that we internally call MakingCommon. Recently, we begun to open source pieces of the library under the name Open Waves. With time, we hope to move most of the internal frameworks to codeplex and share them with others.

PageData Adapters

An interesting functionality (only if you implement EPiServer based solutions, and it still waits for its turn to be moved to Open Waves) is provided by PageDataAdapters. PageDataAdapters started as a mini project of mine 2 years ago but has been developed by couple of other Wave Makers since then. The inspiration for it came from Castle Dictionary Adapters, but feature-set was influenced by patterns employed by model oriented methods, OR mappers, and AOP paradigm. The underlying assumption is that when implementing a CMS 95% of the code is just reading data entered by editors and this is where PageDataAdapters provide most of the functionality. As soon as you see the first example you will think to yourself: “This is the same thing as PageTypeBuilder…”, and in a way you will be right. But it will be a surprise to learn that initially we have not even planned to generate page types from the classes (but now we do). Now some of the usage examples (if you are familiar with what PageTypeBuilder does you will have no problem understanding them).
[PageTypeDefinition]
public abstract class Article
{
    [Property(Required = true)]
    public abstract string Title { get; }

    [XhtmlProperty(DefaultValue = "")]
    public abstract string Body { get; }

    [Parent]
    public abstract ArticleCategory Category { get; }

    [PreviousSibling]
    public abstract Article PreviousArticle { get; }

    [NextSibling]
    public abstract Article NextArticle { get; }

    public abstract IEnumerable<Article> RelatedArticles { get; }
}
As you can see, to define a model for an article page we don’t have to use any of EPiServer related classes. It is pretty clean and self explanatory. We can use model classes to define properties of the class. We can use attributes to customize how values of the properties will be resolved in runtime and how they will be generated in the page type. Here is another example showing how article category can be modelled.
[PageTypeDefinition]
[AllowedChildrenPageTypes(typeof(Article))]
public abstract class ArticleCategory
{
    [Property(BuiltInProperties.PageName)]
    public abstract string Name { get; }

    public abstract Person ContactPerson { get; }

    [Children]
    public abstract IEnumerable<Article> Articles { get; }
}
Now after the model is defined we can use it in the templates. Thanks to richness of the model templates can be much simpler and cleaner. All we have to do is derive the template from generic TemplatePage<TModel> to get access to Model property of type TModel.
public partial class ArticleTemplate : TemplatePage<Article>
{
    ...
}
<h1><%: Model.Title %></h1>
<div>
<%= Model.Body %>
</div>
Contact person: <%: Model.Category.ContactPerson.Name %>
I hope this very simple example illustrates how easy it is to traverse page tree without a need to use any of EPiServer infrastructure in the template code or markup.

An important aspect of the framework is that it is extensible in many ways and places. None of the attributes used in the examples is special or expected by the framework. All of them implement interfaces that extend the runtime behaviour or affect page type generation. For example this is a source code for the ParentAttribute class.
[AttributeUsage(AttributeTargets.Property)]
public class ParentAttribute : Attribute, IPropertyValueResolver
{
    public TypedValue ResolveValue(PageData pageData, string propertyName)
    {
        return TypedValue.From(pageData.ParentLink);
    }
}
IPropertyValueResolver interface is responsible for resolving the value of a property. In most cases resolution means getting the value from PageData, but in this case we’re returning a PageReference to the parent. Now, since what we return is a PageReference, how come Article.Category can be of type ArticleCategory. This is possible thanks to automatic conversions that the framework tries to apply looking at the type of the value returned by a resolver and type of the property (PageReference –> ArticleCategory in the example).
Built in conversions include among others:
  • PageReference –> PageData
  • PageReference –> Url
  • LinkItem –> PageReference
  • LinkItem –> PageData
  • int –> Enum
Creating custom property value resolvers and property value converters allows us in most cases to abstract away EPiServer internals and lets us focus on creating clean and easy to understand information models for the sites we develop.

Future


The future of PageDataAdapters is not quite clear. We have noticed how PageTypeBuilder grew to be a de facto standard library used in EPiServer projects and we know that parts of our framework duplicate functionality of PageTypeBuilder. At the same time we really like the abstraction on top of PageData provided by PageDataAdapters. Also, we have quite a few live projects using the library so we cannot just kill it even if we wanted :P

For now we’ve decided that we want to open source it with the rest of the reusable code we maintain internally. I’ll be very happy to hear any comments you may have about the above and I do encourage you to check Open Waves (even though we have barley started the migration to codeplex).

No comments: