One of the more usefull extension points in EPiServer is the ability to create custom properties. That and GUI Plug-Ins. Those 2 extensions are probably the easiest and most used way to extend the functionality in EPiServer CMS. So how do you create a custom property? Well there are several ways to learn it. You can either go to one of our courses at EPiServer, and maybe even meet me, or you could read an article about it on http://world.episerver.com. It is actually a good article written by one of our developers in Ukraine, Denis Yakovlev. But you could also read this blog entry.
So why am I writing a blog entry. Well, mostly because I think it is an interesting subject that a lot of EPiServer developers would have good use of. And I also feel that there might be some things that isn't really covered or explained in the article. I might be wrong...it was a long time since I read it. But anyhow...here is my explanation to custom properties.
First off I need to create 2 classes. Not one, but 2 classes. The reason for this is that EPiServer decided to split layout from logic a bit more in CMS 5. A very common split that most of us recognize. These two classes have to inherit from PropertyData and PropertyDataControl respectively. The PropertyData class is responsible for "handling" the actual value of the property, while the PropertyDataControl is responsible for creating the visual parts of it. EPiServergenerally recommends that you don't inherit from PropertyData directly, but instead inherit from one of the other PropertyData classes such as PropertyString or PropertyBoolean. By inheriting from one of those instead, you only have to create the extended functionality and not the core pieces. This is a good practice that I do recommend even though I wont do it in this blog post. There is basically at least one implemented property for each of the available storing types, bool, string, int and so on. The same thing is true when it comes to the PropertyDataControl class. You should try to find a less abstract base class if possible. There are several available in the framework, such as PropertySelectControl and PropertyStringControl. Those classes will automagically create the actual visual controls for you and all you have to do is populate them with values.
So...What am I building. Well, there is a little issue when using the "Selected/Not Selected" property. It will return true or null which is kind of annoying in a couple of different scenarios. So I'm creating a True/False/Null property.
The first step in this implementation is to create 2 classes. The first one is called TrueFalseNullProperty and the second one is called TrueFalseNullPropertyControl. They inherit their base classes as described earlier. The code looks like the following:
[code:c#]
public class TrueFalseNullProperty : PropertyData
{ }
public class TrueFalseNullPropertyControl : PropertyDataControl
{ }
[/code]
The first class I will tackle is the TrueFalseNullProperty. First off I wanna override a couple of different methods and properties. I will go through them one at the time and explain why I'm overriding them. The first mthod to override is the SetDefaultValue(). This is a method that is responsible for "resetting" the property value. It will be called from the base class' constructor. This means that it will be the first method called in you class, even before your contructor. But before I can add my override I have to add the backing field which will be a Nullable<bool>. This is also the first place where you run into ThrowIfReadOnly(), which is a helper method I get from the base class. Since PageData objects are read only by default, the user needs to call CreateWriteableClone() before writing to the value. The reason has to do with cacheing. But, to enforce this, every time I change the value of my property, I wanna make sure that the property is actually writeable and not read only. The ThrowIfReadOnly() method will basically just throw an exception if the property is in read only mode and simply return if not. So after adding the backing field and the SetDefaultValue the class looks like the following:
[code:c#]
public class TrueFalseNullProperty : EPiServer.Core.PropertyData
{
bool? _value;
protected override void SetDefaultValue()
{
ThrowIfReadOnly();
_value = null;
}
}
[/code]
That's it. It's not complicated than that. I decided that null would be an appropriate value, but that depends on you property of course. At the same time as SetDefaultValue is called from the base class, the IsNull property of the class is also set to true, so I don't have to take care of that.
Next on the agenda is the parsing methods. When you property is serialized, for example when exporting data from the system, you properties value will be stored as a string. Basically, the export system will just use the XmlSerializer on your class and store the value in an xml-format. When importing the values again, your class will have to parse the string and populate the class. There are 2 parse methods. One takes the serialized value, parses out the value and then sets the value on it self. The other one takes the serialized value, parses it and returns an instance of you class with its value set to the parsed value.
So the implementation for my class would look like the following:
[code:c#]
public override PropertyData ParseToObject(string str)
{ TrueFalseNullProperty prop = new TrueFalseNullProperty();
bool value;
if (bool.TryParse(str,out value))
prop.Value = value;
return prop;
}
public override void ParseToSelf(string str)
{
bool value;
if (bool.TryParse(str,out value))
this.Value = value;
else
this.Value = null;
}
[/code]
After that there is only one last method to override. But I will wait with doing this a little and instead start overriding the properties that need overriding. First out is PropertyValueType. This method returns an object of type System.Type, which should be the type of the object returned from the Value property. For my TrueFalseNullProperty, that is actually a integer. The reason for this is that I'
m going to store the value as an int in the database. Explanation will follow. So the property implementation is created as follows:
[code:c#]
public override Type PropertyValueType
{
get { return typeof(int); }
}
[/code]
Next up is another simple property, Type. This property does NOT return an object of type System.Type, but instead returns a value from the PropertyDataType enum. It represents the type to be used for storing my value in the database. The database stores property values in different columns depending on what type it is. In my case I choose to store the value as an integer, where 1 means true and -1 means false. I could of course also store it as a string of something else, but for now, the integer approach works fine. If I had got the urge to choose PropertyDataType.Boolean, I would be faced with a tiny problem that would destroy my property. The EPiServer database actually stores a false value as null. So integer it is.
[code:c#]
public override PropertyDataType Type
{
get { return PropertyDataType.Number; }
}
[/code]
And then one of the most important properties...The Value property. This is the "main" property of my class. It returns the VALUE of the property. In the get accessor you should first check the IsNull property and then if it is false return an object of the type returned in PropertyValueType, which means int in my case. So it should look like the following:
[code:c#]
get
{
if (IsNull)
return null;
return (_value.Value) ? 1 : -1;
}
[/code]
The set accessor on the other hand is a little more complicated. Or rather, the implementation is simple, but the inne workings is a little complicated if you don't know what is happening. In the set accessor all you have to do is call the base class' SetPropertyValue method. It takes in the value and a delegate that sets the actual value. So...why do I call that method instead of just setting the value. Well, the SetPropertyValue method will check the value for null before calling the delegate. If the value is null, it will skip calling the delegate and instead call SetDefault value and set the IsNull property to true. In the delegate I will just set the value of another property that I am creating in a little while. However, since my Type property returns PropertyDataType.Number the system will actually set the Value property using an integer. So because of that I have to convert the int value to bool. So the complete implementation looks like the following:
[code:c#]
public override object Value
{
get
{
if (IsNull)
return null;
return (_value.Value) ? 1 : -1;
}
set
{
bool? b = null;
int intValue = (int)value;
if (intValue == 1)
b = true;
else if (intValue == -1)
b = false;
SetPropertyValue(value, delegate() { BoolValue = b; });
}
}
[/code]
And now the BoolValue property. The actual implementation of setting the value as well as a typed way of getting hold of the value. The type will be Nullable<bool> so the get accessor only returns _value. The set accessor on the other hand is a bit more complicated. First of all I have to call ThrowIfReadOnly() since I am changing my value. Next I should check if my value qualifies as null, and if so Ie should call Clear(). Clear() is another helper method from the base class. It basically sets the IsNull property to null and calls SetDefaultValue(). If the value isn't null, I verify that the value is changed before just setting the value and calling Modified(). Modified() is yet another helper method from the base class. It will set IsNull to false and the IsModified property to true. So the implementation for my TrueFalseNullProperty would be something like this:
[code:c#]
public bool? BoolValue
{
get { return _value; }
set
{
ThrowIfReadOnly();
if (value == null)
{
Clear();
}
else
{
if (value != _value || IsNull)
{
_value = value;
Modified();
}
}
}
}
[/code]
And now the other class, which you have probably forgotten by now. You know...the PropertyDataControl class. But before I can implement that, my PropertyData class has to give the system access to it. To do that, I override the CreatePropertyControl() method. It returns an IPropertyControl interface which my class automatically implements by inhereting from PropertyDataControl. It is a simple method implemented as follows:
[code:c#]
public override EPiServer.Core.IPropertyControl CreatePropertyControl()
{
return new TrueFalseNullPropertyControl();
}
[/code]
The actual implementation of the PropertyDataControl is actually done by overriding 4 methods and adding a property. It could be more complicated, but I wont make it more complicated here.
First out is the property I want to add. It's just a helper property. The base class provides us with a property called PropertyData which will give us a way to get a hold of the property data object connected to the control. In my case that is a TrueFalseNullProperty, so I create a little helper property as follows:
[code:c#]
public TrueFalseNullProperty TrueFalseNullProperty
{
get { return this.PropertyData as TrueFalseNullProperty; }
}
[/code]
After that I get down and dirty with the overrides. First out of the overrides is CreateDefaultControls(). The Create___Controls() methods are responsible for creating and adding the controls needed to render the Property's interface. There are three methods available. One for the view mode, CreateDefaultControls(). One for the edit mode, CreateEditControls(). And one for One Page Edit, CreateOnPageEditControls(). In my case I don't care about on page editing, so I skip that method. But if you want to implement that feature you have to override the SupportsOnPageEdit property as well as the ApplyOnPageEditChanges() method. But as I said, I wont.
So in the CreateDefaultControls() I will basically just write out true or false if the property has a value. But writing out something from a control like this, I have to create a webcontrol and add it to the controls tree. So it would look like this:
[code:c#]
public override void CreateDefaultControls()
{
if (!TrueFalseNullProperty.IsNull)
{
Literal l = new Literal();
l.Text = TrueFalseNullProperty.BoolValue.ToString();
this.Controls.Add(l);
}
}
[/code]
The CreateEditControls() is allmost as simple. It has to create the controls used for the edit mode. But it should only create the control, not set its value. That is done in another method called SetUpEditControls(), which is called from my CreateEditControls() method. The reason for this implementation is that by doing it like this, you can inherit the control and make changes to the assignment of the controls value. So the Create... Method creates the control and the SetUp... sets the value. In my case I want to use a dorp down list. I also have to store a reference to the control I'm using for future use. So the implementation looks like the following:
[code:c#]
DropDownList _editControl;
public override void CreateEditControls()
{
_editControl = new DropDownList();
this.Controls.Add(_editControl);
SetupEditControls();
}
protected override void SetupEditControls()
{
_editControl.Items.Add(new ListItem("Null", ""));
_editControl.Items.Add(new ListItem("True", "1"));
_editControl.Items.Add(new ListItem("False", "-1"));
if (!TrueFalseNullProperty.IsNull)
_editControl.SelectedValue = TrueFalseNullProperty.Value.ToString();
}
[/code]
And then the last method, ApplyEditChanges(). This is where you move the value from the edit controls to the actual property. It isn't very hard to implement. Basically I wanna take the value from the DropDown and parse it to a useful value and set the PropertyData objects value. Setting the value is actually done from the base class by calling SetValue(), which will do all the dirty work for us. So the implementation is basically:
[code:c#]
public override void ApplyEditChanges()
{
if (_editControl.SelectedIndex > 0)
{
SetValue(int.Parse(_editControl.SelectedValue));
}
else
{
SetValue(null);
}
}
[/code]
Now I am allmost done. This is the entire implementation, except for one little thing. I need to get this property "registered" in the EPiServer system. This is done using attributes. In this case it is an PageDefinitionTypePlugInAttribute. It can set a whole heap of values that will cause it to get the different values translated and things, but I will keep it simple. I will give my property a name and thats it. So I add an attribute looking like the following to my TrueFalseNullProperty class.
[code:c#]
[PageDefinitionTypePlugIn(DisplayName="True|False|Null")]
[/code]
So... That would probably be my longest blog post ever. It's more of an article, but hey...calling it a blog post adds entries to my blog. I wrote it all in trusty Notepad. So if you find any errors, I'm so sorry. But if you send me an e-mail or add a comment about it, I will try to correct it as soon as possible.
Source code available for download here: TrueFalseNullProperty.zip (1.42 kb)