A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

crli
Jul 17, 2009
  2538
(0 votes)

Jumping on the page type bandwagon

With the sun shining and the thermometer approaching body temperature I thought it was god time to have some fun. Quick from thought to action I fired up my trusty black warrior to explore another white spot on the map.

My objective for the day was to get to get aquainted with the page type builder. One of those really neat features your manager never will understand.

I was pleased from the first moment to notice that after including it in my solution and adding my page type it worked right away. It's just like magic, and I cannot but love the firm presence of a piece of text sitting in my source code repository.

The natural next step was learning about the inner workings. Living on the practical side of the fence I tend to learn best by using, exploring and finding interesting aspects to change. With the using already under my belt I ventured into the exploration phase.

Some mental mapping revealed some logical roles:
 * An API to map code to episerver page types and the discovery thereof
 * Synchronization between code and database for page type settings and properties
 * A data factory wrapper to access the typed pages

Looking for interesting aspects to change I first took aim at the startup process. The page type builder uses a very cleverly constructed plugin attribute to trigger the build up and synchronization the page types. Beeing slightly paranoid I wanted this a bit more explicit. At the price of a tiny bit of extra code comes a cosier feeling of control.

 protected void Application_Start(Object sender, EventArgs e)
 {
    PageTypeBuilder.Initializer.Startup(PageTypeBuilder.InitializerOptions.CallProperties);
 }

The actual initializer then waits until the opportune moment (EPiServer is initialized during the first request) and then synchronizes:

 public static void Startup(InitializerOptions options)
 {
  EPiServer.Web.InitializationModule.FirstBeginRequest += InitializationModule_FirstBeginRequest;
 }
 
 static void InitializationModule_FirstBeginRequest(object sender, EventArgs e)
 {
  PageTypeBuilder.Synchronization.PageTypeBuilder.UpdatePageTypes();
 }

The next change is about pushing typed page data objects into the EPiServer APIs. One particular aspect I like about the page type builder is the way it fits with the existing model. Re-tailoring for an even tighter fit I latched onto the data factory event system to push the typed pages down the stack and into EPiServer.

 public static void Startup(InitializerOptions options)
 {
  //...
  EPiServer.DataFactory.Instance.LoadedPage += DataFactoryInstance_LoadedPage;
 }

 static void DataFactoryInstance_LoadedPage(object sender, EPiServer.PageEventArgs e)
 {
  Type type = TypeResolver.GetTypeForPageType(e.Page.PageTypeID);
  //...
  e.Page = Activator.CreateAndPopulateInstance(e.Page, type);
 }

The final and most fun change is related to the property usage. This is how strongly typed properties can be constructed:

 [PageTypeBuilder.PageTypeProperty]
 public string SecondaryBody
 {
  get { return GetPropertyValue<TextPage, string>(p => p.SecondaryBody); }
  set { SetPropertyValue<TextPage, string>(p => p.SecondaryBody, value); }
 }

This approach while very pragmatic is not the most readable. The property getters and setters are responsible for using the correct property data and the lambdas provides some nice refactoring support. Inspired by one of my colleagues I decided to extend this using one of my favorite toys: DynamicProxy2. After introducing a dynamic activator the code in the page type would look like this:

 [PageTypeBuilder.PageTypeProperty]
 public virtual string MainBody { get; set; }

Slightly less, isn't it? Of course the code for the activator itself is a bit longer, but not that long. Instead of creating a regular page data instance it uses DynamicProxy2 to create a special instance with a method call interceptor (it overrides and intercepts the getting and setting of properties). The interceptor then analyzes calls to the page data objects and uses GetValue/SetValue instead of calling the actual properties.

Update (20 july): Krzysztof pointed out a more elegant solution using proxy generation hooks and the code has been updated. 

    protected override TypedPageData CreateInstance(Type typedType)
    {
        (TypedPageData)generator.CreateClassProxy(typedType, options, interceptors);
    }

Creating the page proxy will allow for this interceptor to be called instead of the reguar property.

    internal class PageTypePropertyInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            string propertyName = invocation.Method.GetPropertyName();
            if (invocation.Method.IsGetter())
                invocation.ReturnValue = page.GetValue(propertyName);
            else
                page.SetValue(propertyName, invocation.Arguments[0]);
        }
    }

The generator hook allows us to select which properties should be intercepted.

    internal class PageTypePropertiesOnlyProxyGenerationHook : IProxyGenerationHook
    {
        public bool ShouldInterceptMethod(Type type, System.Reflection.MethodInfo memberInfo)
        {
            return memberInfo.IsGetterOrSetterForPropertyWithAttribute(typeof(PageTypePropertyAttribute))
                && memberInfo.IsCompilerGenerated();
        }
        //...
    }

Boy, that was fun! And educative. Hope I didn't overdo it and that some of this (thoroughly untested) experimentation can be of any use. Oh, and here's the code in case anyone is interested.

Jul 17, 2009

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP: Learning Optimizely Just Got Easier: Introducing the Optimizely Learning Centre

On the back of my last post about the Opti Graph Learning Centre, I am now happy to announce a revamped interactive learning platform that makes...

Graham Carr | Jan 31, 2026

Scheduled job for deleting content types and all related content

In my previous blog post which was about getting an overview of your sites content https://world.optimizely.com/blogs/Per-Nergard/Dates/2026/1/sche...

Per Nergård (MVP) | Jan 30, 2026

Working With Applications in Optimizely CMS 13

💡 Note:  The following content has been written based on Optimizely CMS 13 Preview 2 and may not accurately reflect the final release version. As...

Mark Stott | Jan 30, 2026

Experimentation at Speed Using Optimizely Opal and Web Experimentation

If you are working in experimentation, you will know that speed matters. The quicker you can go from idea to implementation, the faster you can...

Minesh Shah (Netcel) | Jan 30, 2026