16 May 2009

EPiServer: Creating a re-usable generic custom property

EPiServer’s custom property functionality does allow for a high level of flexibility over what kind of data you want to store in EPiServer. However, things start to get tricky when you need to store more complex, object-based data in a property.

This article outlines a technique for creating a re-usable generic custom property that makes the creation of object-basesd properties much easier. It provides an abstract base class for property definitions that is derived from EPiServer.Core.PropertyLongString, using XML Serialisation to store the data as a serialized string whilst exposing it as a typed object.

There are four main components to this solution, though you can download the fully annotated source code here (ZIP archive, 5KB).

1. The data class that encapsulates the property data

The data class served up by the property should be serializable, so will require at the very least a public constructor and some public members. The code below shows a simple data class that will be used by this example:

using System;
using System.Xml.Serialization;

namespace BenMorris.EPiServer
{
    [Serializable]
    [XmlRootAttribute("employee")]
    public class Employee
    {
        public Employee()
        {
        }

        [XmlElementAttribute("forename")]
        public string Forename
        { get; set; }

        [XmlElementAttribute("surname")]
        public string Surname
        { get; set; }

        [XmlElementAttribute("email_address")]
        public string EmailAddress
        { get; set; }
    }
}

2. The property definition generic base class

Creating the custom property definition is the trickiest part of the solution, requring some careful over-riding of EPiServer properties and methods. The code in this example is based on some useful work published on Henrik Nyström’s blog, where he offered a nice technique for implementing a custom property that returns a custom type from the Value property rather than the underlying EPiServer property type.

I have extended this, deriving a custom property from EPiServer.Core.PropertyLongString and incorporating some XML serialization routines. The internal representation of the data remains an EPiServer long string – i.e. the serialized object – while the class serves up a strongly-typed object to anybody consuming it. The next step is to turn this working property into an abstract class based upon a generic parameter – this allows the class to be used as a base class for whatever class you want turn into a custom property.

The code listing is too long to show here (though you can download it and have a look) though the main challenges it had to over-come are:

  • Maintaining an internal representation of the underlying data so you can keep track of changes.
  • When over-riding the Value property you have to be prepared to handle input which may be the typed object value or the serialized representation of the object.
  • Over-riding methods that allow the property to be loaded, saved and parsed.

3. Implementing the generic base class

Deriving a new property definition class from the generic base class is pretty simple – the only method that you have to implement is CreatePropertyControl, which provides a link between the property definition class and the class that provides the EPiServer interface for the property. The code below shows an implementation that uses our Employee class:

using System;
using EPiServer.Core;
using EPiServer.PlugIn;

namespace BenMorris.EPiServer
{
    [Serializable]
    [PageDefinitionTypePlugIn(DisplayName = "Employee page property")]
    public class EmployeeProperty : BombayCrow.EPiServer.GenericProperty<Employee>
    {
        public override IPropertyControl CreatePropertyControl()
        {
            // Provide a reference to the property control.
            return new EmployeePropertyControl();
        }

    }
}

4. The interface class for the property

The final step is to provide an interface for the property in EPiServer. The example below is a basic implementation that overrides two methods: CreateEditControls to provide an interface in EPiServer’s Edit mode and ApplyEditChanges to save the user input to the underlying property value. I have also provided a private method called GetPropertyData that is used to fetch a valid Employee object from the underlying property data.

using System;
using System.Web.UI.WebControls;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.Web.PropertyControls;
using EPiServer.Web.WebControls;

namespace BenMorris.EPiServer
{
    public class EmployeePropertyControl : PropertyStringControl
    {
        public override void CreateEditControls()
        {
            //* Create the controls - you will probably want to introduce a little formatting here...

            Employee data = GetPropertyData();

            TextBox ForenameTextBox = newTextBox();
            ForenameTextBox.ID = "ForenameTextBox";
            ForenameTextBox.Text = data.Forename ?? string.Empty;
            base.Controls.Add(ForenameTextBox);

            TextBox SurnameTextBox = newTextBox();
            SurnameTextBox.ID = "SurnameTextBox";
            SurnameTextBox.Text = data.Surname ?? string.Empty;
            base.Controls.Add(SurnameTextBox);

            TextBox EmailAddressTextBox = newTextBox();
            EmailAddressTextBox.ID = "EmailAddressTextBox";
            EmailAddressTextBox.Text = data.EmailAddress ?? string.Empty;
            base.Controls.Add(EmailAddressTextBox);
        }

        public override void ApplyEditChanges()
        {
            //* Get the updated values from the form controls
            Employee data = GetPropertyData();
            data.Forename = ((TextBox)base.FindControl("ForenameTextBox")).Text;
            data.Surname = ((TextBox)base.FindControl("SurnameTextBox")).Text;
            data.EmailAddress = ((TextBox)base.FindControl("EmailAddressTextBox")).Text;
            base.SetValue(data);
        }

        private Employee GetPropertyData()
        {
            //* Fetch the underlying data object - ensure that a null is not returned
            Employee data = (Employee)this.PropertyData.Value;
            if (data == null)
            {
                data = newEmployee();
            }
            return data;
        }
    }
}

Download the code

The full code for this proof of concept lives here (ZIP archive, 5kb).

Filed under ASP.NET, CMS.