EPiServer  /  CMS June 19, 2011

Page Type Builder 2 Preview 2

A few weeks ago we released the first preview of Page Type Builder 2. This weekend a second preview was released. While the first preview offered new and exciting functionality with support for property settings and a new concept called property groups this version has it’s aim focused at addressing some of the limitations to the functionality that Page Type Builder has. It does this in two ways, with a new project called PageTypeBuilder.Migrations and by automatically changing the type of properties in some situations.

Background – Limitations to the synchronization of page types with the code

While Page Type Builder supports adding page types and page definitions (properties on page types in EPiServer) based on classes and properties in code it doesn't support removing the same. It’s ability to update existing page types and page definitions has also been somewhat limited. It only renames page types if they are mapped using the page type’s GUID and it doesn’t rename or change the type of page definitions.

These limitations exist because Page Type Builder has to use names to identify page types and page definitions and because EPiServer permanently deletes data when we change the type of a property. While there have been many discussions about these limitations as well as ideas for working around them none has been bullet proof. And considering the risk of users of Page Type Builder losing data they haven’t been implemented.

One idea that seemed very promising though was brought up by Lee Crowe in a comment to the post about the previous preview release; automatically change the type of properties if their value types are compatible. After some investigation I found that this exact idea wouldn’t work out of the box, but with some modifications.

Automatic updating of some page definition types

The reason it wouldn’t work out of the box is that even though the value type of property types are the same they can be stored in different ways in the database. For instance both a property of type PropertyString  and a property of type PropertyLongString have string as value type but the PropertyString will be stored in a column that is limited to 255 characters.

What would be safe though would be to automatically change the type of a page definition (an EPiServer property on a page type) if the old and new type have the same data type. The data type is an enum (PropertyDataType) and all property types (descendants of PropertyData) must specify which data type it maps to to use using it’s Type property. When EPiServer saves an instance of the property it uses its data type to determine what type of column it should be stored in in the database. In other words, as long as the old and the new property types have the same data type changing the type of a page definition should never lead to data being lost.

Therefore, in this release Page Type Builder will automatically update the types of properties if the old and the new type have the same data type. This for instance means that Page Type Builder will automatically change the type to and from PropertyLongString and PropertyXhtmlString. It will however not update to and from PropertyString and PropertyXhtmlString as they have different data types.

Migrations

Another idea for addressing the limitations of Page Type Builder that has been discussed for a while has been to introduce functionality to explicitly remove and rename page types and page definitions. Something similar to the many migration frameworks that exists for databases, such as Rails Migrations Migrator.NET.

Migration frameworks usually allow developers to create individual migrations that contain three things: a number (or a timestamp), a method for applying the migration (often named Up) and a method for undoing the migration (often named Down). In the methods the developer can add new tables or columns, rename existing database entities and move data around. The frameworks keep track of which migrations have been previously applied and executes any new ones.

This often of works great for databases but it isn’t a perfect fit for Page Type Builder as it already addresses adding new things and doesn’t communicate directly with the database, thus making moving data around so as to not lose it harder. The general concept however should work. Therefore this version of Page Type Builder ships with a new project/assembly named PageTypeBuilder.Migrations. It differs somewhat from regular migration frameworks as it only has a method for applying the migration and no method for undoing it.

While this new assembly ships with Page Type Builder using it is fully optional. The core Page Type Builder assembly doesn’t require it. And while it gives you power to easily remove or modify things without going into admin mode you should remember that with power comes responsibility. In other words – it should be used with some caution.

Using Migrations

In order to use the migrations project you need to have the assembly loaded in your application domain (reference it in your project) along with the regular Page Type Builder assembly. Once you have that you create migrations by creating classes that implement the IMigration interface, which looks like this:

namespace PageTypeBuilder.Migrations
{
    public interface IMigration
    {
        void Execute();
    }
}

As you can see it has a single method named Execute which has to be implemented. This method will be invoked just before Page Type Builder does its regular synchronization.

In addition to implementing the IMigration interface there are a number of strict requirements for migration classes:

  • They have to be named MigrationX where X is an integer larger than 0. X also cannot start with 0, so Migration1 is a valid name while Migration01 is not.
  • All migrations have to reside in the same namespace.
  • All migrations must be numbered in a consecutive sequence. That is, you cannot have Migration1 and Migration3 without having Migration2.
  • All previously applied migrations must be present in the application domain. In other words, you cannot remove old migrations. The history of applied migrations is stored in the database using DDS.

If any of the above requirements aren’t met an exception will be thrown and further execution will be halted until the problem has been addressed or migrations have been disabled.

These requirements may seem strict but they are there to help us prevent problems that we may otherwise run into. For instance, the strict naming standard and the requirement for all migrations to be in the same namespace is there to prevent two developers from both checking in a migration with the same number into source control. The requirement for all migrations to be present in the application domain is there to address the situation where two developers use the same database and one of them has removed something while the other still has an old version of the source code, effectively re-adding whatever the first developer just removed.

The Migration class

While the framework will treat any non-abstract class that implements the IMigration interface as a migration it also ships with an abstract class named Migration that we can inherit from. By doing so we gain access to some helper classes and methods that make common tasks easier to do. For instance, if we wanted to create a migration that changed the type of the a property named Preamble in a page type named Article we could implement it like this:

using EPiServer.Core;
using PageTypeBuilder.Migrations;

public class Migration1 : Migration
{
    public override void Execute()
    {
        PageType("Article")
            .PageDefinition("Preamble")
                .ChangeTypeTo<PropertyLongString>();
    }
}

The helper methods, such as PageType, PageDefinition and ChangeTypeTo illustrated above, all share two conceptual characteristics. First they make it easy to implement common tasks in migrations. Second they do it in a forgiving way. For instance, if there isn’t any page type named Article the above code wouldn’t throw an exception. Execution would just pass through it and it would log that the migration tried to retrieve a page type with that name but failed to find one.

I think the forgiving nature of the helpers is good in scenarios where we’re deploying to a production environment. It might however cause some annoyance during development as we won’t get any feedback as to why a migration didn’t work (except for in the log) and we would have to create a new migration or clear the log of applied migrations in the database. This is something I plan to play with a bit myself and definitely would appreciate feedback about.

Below is further examples of the helpers that ships with the current version.

PageType("Article").Delete();

PageType("Article").Rename("Chronicle");

PageType("Article")
    .PageDefinition("Preamble")
        .Delete();

PageType("Article")
    .PageDefinition("Preamble")
        .Rename("MainIntro");

PageType("Article")
    .PageDefinition("Preamble")
        .ChangeTypeTo<PropertyXhtmlString>();

Disabling migrations through configuration

While I wouldn’t recommend switching back and forth from using migrations in development there might be times when we are forced to, such as if another developer with whom we share the database has created a migration and went home without checking it in (in this scenario I would advice to temporarily create dummy migration with the same number though). However, for production environments with multiple web servers it might be advisable to disable migrations on all but one web server.

Anyhow, to disable migrations from being both validated and applied at startup we can add a new configuration section to web.config and set the disabled attribute to true, like this:

<configuration>
  <configSections> 
    <section name="pagetypebuilder.migrations" 
        type="PageTypeBuilder.Migrations.Settings, 
            PageTypeBuilder.Migrations"/>
  </configSections>
  <pagetypebuilder.migrations disabled="true" />
</configuration>

Other changes

Apart from the changes and new functionality described above there has been quite a few changes internally to Page Type Builder. If you download the release you will also find that it has a new dependency to the IoC container Autofac. While I haven’t tested it this release should be backwards compatible with previous releases though, with one exception: properties in interfaces annotated with the PageTypeProperty attribute will no longer be mapped to properties on page types unless of course the implementing class also annotates the property with the PageTypeProperty attribute. Allowing (EPiServer) property declarations in interfaces, which was introduced in version 1.3, demanded quite a lot of complexity, especially for validation and seeing how the new concept of property groups made the functionality redundant I decided to scrap it.

Feedback

While I don’t consider this release stable that’s primarily because I haven’t used it myself on a real site yet or gone through the usual pre-release “just-to-be-sure-profiling”. The code is however covered by an extensive set of tests and executable specifications (from which the output is shipped with the release in the specifications.html file). Therefore, I wouldn’t hesitate to use it myself if I was working on a project that wasn’t about to go live very soon and I would very much appreciate it if anyone working on such a project would give it a try. Feedback from real world usage is the only way to guarantee a nice API and true stability.

Even if you don’t use this new release I would very much appreciate any and all feedback though. Especially as the changes made in this release are somewhat experimental and I’m sure that they can be further improved.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

My book

Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!

More about EPiServer CMS