Properties are at the core of EPiServer CMS. Hence, understanding EPiServer's properties is essential for any EPiServer developer with ambitions for building great things.
In this and a series of subsequent posts I’ll provide an in-depth guide to creating custom properties for EPiServer CMS. In this first post we’ll focus on theory. We’ll look at what properties are in EPiServer and at how EPiServer’s property model works. We’ll also study PropertyData, the abstract class that all EPiServer properties must inherit from and discuss different strategies that we can use when creating custom, or specialized, properties for EPiServer.
What is an EPiServer Property?
In EPiServer CMS an actual web page (such as www.mydomain.com/about) is represented by the PageData class. A PageData object, just like other .NET classes has a number of properties, such as PageData.PageName and PageData.LinkURL. This standard type of properties will be referred to as “code properties” in this tutorial.
One of the PageData class’ code properties is named Property and is of type PropertyDataCollection. It contains a number of objects of different types that all inherit from a class named PropertyData.
These objects, from now on referred to as “EPiServer properties” or simply “properties”, all have in common (among other things) that they have a code property named Value. It is these values that make up the content of the page. It’s these values that the editor specifies in EPiServer’s edit mode.
The values can be accessed using PageData.Property[“PropertyName”].Value or by the shortcut PageData[“PropertyName”] as well as the PageData.GetValue method. The open source project Page Type Builder provides other, strongly typed, ways to access property values.
The type of a property’s value is determined by the property it self. For instance, the built in property type PropertyString’s Value property will return objects of type string and the property type PropertyPageReference will return values of type PageReference. In both these cases the type of the value is implied in the property type’s name. This is however not always the case. For instance, the property type PropertyPageType will not return an instance of the PageType class but an integer which is the page type’s ID.
How properties are added to pages
What properties are found in a PageData object’s Property collection is defined by the PageData object’s PageType. The PageType class has a collection of PageDefinitions which essentially is the relationship between a PageType and it’s properties. That is, if we add a new property of type PropertyString to a page type in EPiServer’s admin mode, or by code using Page Type Builder, a new PageDefinition is created. The PageDefinition contains information about the page type, the type of property and the property’s name on the page type.
Other responsibilities of properties
Apart from carrying values for pages properties also serve two purposes. They defines how the value should be entered by the editor in edit mode and how it should be displayed if the property is rendered by the Property control (EPiServer:Property). It does so indirectly by a method named CreatePropertyControl that returns an instance of the IPropertyControl interface.
Settings
In version six of EPiServer CMS a new and long sought after feature was introduced, property settings. Property settings enables us as users of properties to specify per-instance-on-a-page-type settings for a property. That is for each property we add to a page type of a specific property type we can specify different settings, either in admin mode or, somewhat crudely programmatically. The most widely used example of property settings is probably the settings for the XHTML property for which we can specify what operations should be available to the editor in the WYSIWYG editor.
Import and Export
EPiServer CMS provides functionality for moving different forms of data, including pages and their property values, between different sites and hosting environments by different means including creating export packages in admin mode and mirroring through web services. When pages are exported it is each individual property’s responsibility to package itself up into a export-friendly format by providing an instance of the RawProperty class. It does so by a method named ToRawProperty that is defined in PropertyData. That method will in turn “serialize” the property’s value to a string by calling the ToString method on the Value (code) property. When importing the property it will recreate the value by calling the PropertyData.ParseToSelf method which is described later on in this post.
What this means is that in order for custom property types to work correctly with import/export/mirroring scenarios we need to ensure that whatever type our custom property exposes as value its ToString method must produce a string from which the ParseToSelf method can recreate it.
To summarize, a property is a class that, when instantiated, has a value of a specific type that can be accessed through its Value (code) property. It also knows how this value should be edited and how it can be displayed. A property is always associated with a PageData object and its value, along with the values of other properties, make up the content of the page.
Standard EPiServer properties
EPiServer CMS ships with a number of ready to use properties that can be divided into two groups, system properties and specialized properties. The system properties are found in the EPiServer.Core namespace and allow us to add values of the standard value types as well as strings and fundamental EPiServer concepts such as references to other pages or XForms to page types. A few examples of these property types, the system properties, are PropertyNumber, PropertyBoolean, PropertyString and PropertyPageReference.
The other group of property types that ship with EPiServer, the “specialized properties”, are found in the EPiServer.SpecializedProperties namespace. These properties represent more complex and/or specialized types of values. Examples of these property types are PropertyXhtmlString, PropertyColor and PropertyImageUrl.
When to create custom EPiServer properties
There are two main reasons why you’d want to create a custom property for EPiServer CMS. You either want to have a property that stores a different type of value than what is provided by the built in property types or you want to change how the property’s value is entered and modified by the editor in EPiServer’s edit mode. Often you want to do both.
Creating a custom property to store a certain type of value is something that can significantly improve the quality of parts of our code as it helps us fight the code smell known as primitive obsession where primitive data types are used as substitutes for classes.
Requirements for an EPiServer property
The minimum that any class that represents a property must do is to inherit from the abstract PropertyData class. However, properties must also be registered with the system. Although this can be done in EPiServer’s admin mode using the Edit Custom Property Types feature (shown below) it is usually done by decorating the class with the PageDefinitionTypePlugIn attribute. Whenever EPiServer starts up, such as after recompilation or a modification to web.config, it will look for classes with this attribute and register them if they haven’t already been registered.
Custom property development patterns
Reuse through inheritance
While, as we just saw, the minimum requirement for a custom property is to inherit from the PropertyData class it is common to instead inherit from one of the built in property types when creating custom properties. In doing so we’re able to reuse already implemented and well-tested functionality and we don’t have to provide implementations for some or all of the abstract methods (which we’ll soon study in detail) that the PropertyData class defines.
This is also something that the EPiServer developers themselves have done quite a lot. In fact, looking at the specialized properties found in the EPiServer.SpecializedProperties namespace none of the inherits directly from PropertyData but from either one of the system property types or from another type of specialized property. For instance, PropertyLinkCollection inherits from PropertyXhtmlString which in turn inherits from PropertyLongString that inherits from PropertyData.
Likewise, if we for instance were to develop a property that would store a number we could probably reuse a lot of functionality from the PropertyNumber class by inheriting from it.
Exposing the value in a strongly typed way
Another thing that is commonly done when developing custom EPiServer properties, especially in those that EPiServer themselves provide, is to expose the value in an additional property of the actual type of the value, as opposed to the Value property that is of type Object. For instance, PropertyPageReference has a (code) property named PageLink of type PageReference and PropertyNumber has a (code) property named Number of type int.
This is in no way a mandatory practice but it is usually convenient to follow this pattern as it makes it easier to access the value of the property in the control that is used for editing it as it removes the need to cast the Value (code) property. Likewise, it also provides strongly typed access to the value for other members of the custom property class. Therefore, when using this pattern its common to put any code that should be executed upon getting or setting the value inside the strongly typed property instead of inside the Value property to ensure that it will always be executed.
Serializing the value to a string
Yet another common pattern for custom property development is to create properties whose value is some custom class and serialize it to a string when its saved. As EPiServer only supports eleven data types that a property’s value can be saved as complex types will need to be serialized to one of those types in order for EPiServer to be able to save it. The PropertyLinkCollection class is one such example among the built-in properties. It’s Value (code) property returns an instance of the LinkItemCollection class while it’s SaveData method returns a string.
The PropertyData class
The PropertyData class plays a major role in EPiServer’s property model as it defines the contract, or interface, that all property types must adhere. It is also the place where the EPiServer developers have decided to put a lot of the infrastructure and utility methods needed both by EPiServer’s own properties and third party properties.
While it's common to inherit from a more specific class than PropertyData when creating a custom property studying the PropertyData class provides good insight into how the property model works, knowledge that can be invaluable if we run into problems.
Abstract methods that must be implemented
PropertyData has a big interface comprised of many methods and properties. Six of these are abstract, meaning that we must implement them if we create a property type that inherits directly from PropertyData. These six, three methods and three properties are
public abstract PropertyData ParseToObject(string value); public abstract void ParseToSelf(string value); protected abstract void SetDefaultValue(); public abstract Type PropertyValueType { get; } public abstract PropertyDataType Type { get; } public abstract object Value { get; set; }When implemented the ParseToObject method should return an instance of the property type parsed from the string it is passed as a parameter. It can be considered the reverse of the ToString method.
The ParseToSelf method should also parse a string representation of the property but instead of returning an instance of the property it should retrieve the value from the parsed property and assign it to itself.
The SetDefaultValue method should when implemented do just what it says, set the the value of the property instance that it’s invoked on to the default value for the type. The default value is usually a “empty” value, but not null, such as zero for property types whose value type is numbers or an empty string for properties whose value type is strings.
The PropertyValueType property should return the type of the object that the property’s Value property will return and expect when set.
When implemented the property named Type should return a value that describes what “data type” the property’s value should be saved as. This indicated by returning a value from the enum named PropertyDataType. The PropertyDataType defines eleven valid types, one of which is obsolete:
- Boolean
- Category
- Date
- FloatNumber
- Form (obsolete)
- LinkCollection
- LongString
- Number
- PageReference
- PageType
- String
When EPiServer saves a property’s value (when saving a page) it will call a method named SaveData which is declared and implemented in PropertyData to retrieve the value to be saved. SaveData will return the property’s Value property (this.Value) unless it has been overridden to return something else. EPiServer will then save that returned object to the database as one of the above listed types. That is, the Type property defines how, using what data type in the database, the value of the property should be saved.
The Value property, when implemented, exposes the property’s value. The value is of the type returned by the PropertyValueType (code) property mentioned above. The Value property is also settable, however as PropertyData implements the IReadOnly interface it should throw an exception if the property isn’t either new or cloned. That is, if the property is part of a page that has just been fetched from the database or the cache the page should first be cloned (using PageData.CreateWritableClone()) before it can be saved. To make it easy to throw an exception if the property is read-only the PropertyData class has a protected method named ThrowIfReadOnly that can be used when the setter is invoked.
The CreatePropertyControl method
Another important member of the PropertyData class, although not abstract, is the CreatePropertyControl method returns an instance of the IPropertyControl interface. That control is used by EPiServer both when displaying the property for editing for the editor in edit mode and when displaying the property on a page (in view mode) using the Property control (EPiServer:Property). The PropertyData class provides a default implementation of the method that returns a PropertyStringControl. We can however override the method to return a different type of control as it’s virtual. We’ll learn more about this method and the IPropertyControl interface in a later post when we look at creating custom properties with custom property controls.
The SaveData method
As mentioned previously the SaveData method should be implemented to return an object of the type defined by the Type (code) property. It is this value that EPiServer will persist to the database. The PropertyData class provides a default implementation that simply returns the Value (code) property.
The IsModified property – Challenges when the value is a reference type
The IsModified property is used by various other classes in EPiServer CMS to identify properties that have been, you guessed it, modified. For instance, when a page is saved the system will only bother to save the values of properties that have been modified, making it crucial that the IsModified property is implemented correctly. For properties that holds value types or immutable reference types (reference types that can’t be modified) doing so i pretty simple. All we have to do is call PropertyData’s Modified method whenever the property’s value is set. But when the property holds a mutable reference type as value things get trickier as it is then possible to change the value without setting the Value property.
To illustrate this problem let’s look at an example with PropertyLinkCollection that ships with EPiServer. Its Value property returns an object of type LinkItemCollection. Let’s assume that we have a PageData object with a property of type PropertyLinkCollection named links.
var linkCollection = (LinkItemCollection) page["links"]; linkCollection.Add(new LinkItem());
The above code will change the value of the links property but the property itself won’t know that it has. There are two ways to handle that. We can either make sure that all of our reference types that are used as values for properties are immutable. That way the only way to change the value of a property is to assign a new value to it. In such as scenario the LinkItemCollection class wouldn’t have an add method and the only way to add LinkItem’s to it would be upon instantiation as constructor parameters. The second, and usually better, way is to add functionality for keeping track of whether it has been modified to the class that is being used as values as well. The property class can then query it’s value if it has been modified. This is the approach that the EPiServer team has taken for PropertyLinkCollection. Using Reflector we can see that the IsModified property of PropertyLinkCollection will return the modification status of its value (the _linkItemCollection field) if the property itself hasn’t been modified.
public override bool IsModified { get { if (!base.IsModified) { return this._linkItemCollection.IsModified; } return true; } set { base.ThrowIfReadOnly(); this._linkItemCollection.IsModified = value; base.IsModified = value; } }
The IsReadOnly property – More challenges when the value is a reference type
Another interesting member of the PropertyData class is the IsReadOnly property. As anyone who has worked with EPiServer CMS for a while knows PageData objects must be cloned using the CreateWritableClone method before they can be updated. The same is true for individual properties. As with the IsModified property this doesn’t pose any significant challenge when a property has a value type or mutable reference type as value. All we have to do then is call PropertyData’s ThrowIfReadOnly method when in the setter of our implementation of the Value property. But for mutable reference types things are trickier. It’s then not enough that the property class ensures that it cannot be modified if it’s read only but the value must also ensure that. Therefore it’s common to implement the IReadOnly<T> (where T is the class itself) interface and ensuring that instance can only be modified if they aren’t read only in classes that are used as values for EPiServer properties. When doing so we must also override the property class’ CreateWritableClone method and make sure that it will not just clone the property but also the property’s value. An example of a class that does this is the PropertyLinkCollection class whose CreateWritableClone method looks something like this:
public override PropertyData CreateWritableClone() { PropertyLinkCollection links = (PropertyLinkCollection) base.CreateWritableClone(); links._linkItemCollection = this.Links.CreateWritableClone(); return links; }
The IsNull property and important considerations regarding empty values
Null plays a special role when it comes to the value of EPiServer properties. If we retrieve the value of a property from a PageData object using the string indexer (page[“propertyName”]) or the Property collection (page.Property[“propertyName”].Value) and that value is null there is a mechanism that will first try to retrieve a non-null value from another page (in the case of dynamic properties or fetch-data-from-functionality) or language version of the same page. For certain types such as collections this poses a challenge as the convention is that an empty collection should be treated as being null. Therefore PropertyData’s IsNull property is important. During development of custom properties we normally don’t have to care about it directly but we should call PropertyData’s Clear method when the value of the property is set to something that should in the end be treated as null (such as an empty collection). The Clear method will in turn ensure that IsNull will return true.
Also, to abide by EPiServer’s conventions custom properties should also return null when their values are “empty” (collections having Count == 0, strings having Length == 0 etc).
Other guides
I hope you found this post, and will find coming post, helpful. Custom property development is a big subject and EPiServer’s property model is quite complex so I think another guide on the topic can only do good. However, with that said there are some other really great guides and tutorials out there on the subject. One such example is this post by Ted Nyberg about EPiServer’s property model. There are also quite a few guides to creating custom EPiServer properties among which this article by Denis Yakolev is probably the most extensive. While it is very old, the book Developing Solutions with EPiServer 4 is also still a good read to understand the fundamentals of EPiServer development.
Next post
In the next post we’ll get our hands dirty and learn how to actually develop a custom EPiServer property.
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.
My book
Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!
Comments
comments powered by Disqus