In my previous post How to create a custom EPiServer property I described how properties work in EPiServer as well as common patterns and practices when creating custom properties. In this part we’ll get our hands dirty by creating a custom property.
This is a long post that provides an in-depth description of how to create a custom property for EPiServer CMS. If you’re intimidated by it’s length, it might be a good idea to first download the sample code and use the post as reference to the code.
As I described in the previous post the decision to create a custom property is usually motivated by one, or both, of two reasons. The first reason is to be able to save different types of objects as property values on pages. The other is to be able to provide a way to input certain types of data for the editor. In other words, the first reason has to do with the type of value that a property can hold and the other how that value can be viewed and edited. In this post we’ll focus on the first reason by creating a custom property that holds a custom value type.
The scenario – storing keywords
In this tutorial we’ll work with a fairly realistic scenario by creating a property for storing a collection of keywords or key-phrases.
It’s quite common to add the ability to add keywords, separated by commas, to pages in EPiServer CMS for use as meta-keywords or as simple content tags. One way to do that is to simply add a property of type PropertyString with a name or help text urging the editor to enter keywords separated by commas to the page type. While that approach works it has two major drawbacks. It isn’t very intuitive for the editor and it often forces us to write code in the presentation layer (in user controls, controllers in MVC etc) of our application in order to be able to treat a single string as a collection of keywords. That code, which usually involves splitting strings, looping etc is often duplicated to multiple places where the keywords are used or at best placed in some form of utility class.
This reeks of a code smell that is often referred to as primitive obsession. While we as developers may know that a property holds a set of keywords the property doesn’t expose it as such but as a single string and we have to embed knowledge about what that string actually contains all throughout our application. A much cleaner approach is to create a property with a value type that clearly communicates what it represents.
Components
Custom properties in EPiServer CMS consists of two plus two components. The first two which are more or less mandatory are the property class, which must descend from PropertyData, and a control that must implement the IPropertyControl interface. These two components are often thought of as the “data part” and the “edit and view part”.
The two other components, which are optional, is a settings class that stores per-property-on-page-type settings and a custom class that is used as the property’s value.
In this series of posts we’ll create all of these components. However, in this post we’ll focus on creating the property class, named PropertyKeyword, and a custom value class, named Keywords. In order to have a functional and useful example at the end of the tutorial we’ll also create a very simple control class but we’ll focus much more on the edit and view aspects of custom property development in a later post, as will we with settings.
A word about testing
I’m an avid advocate of test driven development (TDD), something that isn’t always easy to practice with EPiServer CMS. When it comes to development of custom properties it’s often quite easy though and I encourage you to practice it. As for this post, and series of posts, I’ll focus on the actual production code. However, in the downloadable code sample you’ll find a test project with examples of how the code presented in this post can be tested. It uses the testing framework, or context specification framework MSpec and you’ll need a test-runner such as TestDriven.NET to execute the tests. Check out my quick introduction to MSpec as well as this post for some more information about the framework.
Creating the value class
Our property should store a collection of keywords which really is just strings. In theory we could therefore let it’s Value property return a generic list of strings (List<string>). There are however a number of reasons why that wouldn’t be a good idea and to why we need to create a custom class that is used as value for the property. For the property to work properly with EPiServer’s export and mirroring functionality its ToString method has to return a serialized representation of the class that can be de-serialized. Since the we are dealing with a collection which is a mutable reference type we also need to ensure that it implements the IReadOnly interface and that the property class can check whether it has been modified.
The below list summarizes what we’ll need to do to correctly create the Keywords class:
- It should hold an iterable collection of string.
- It should be possible to add strings to it.
- It should implement IReadOnly<Keywords>.
- It should provide a mechanism to check if it has been modified.
- The ToString method should produce a string that can be used to recreate the object.
- It will have a static method that can be used to de-serialize Keywords (reverse ToString).
- It should have a method for copying the object that the property class can use.
- It’s Equals method should be overridden to correctly compare equality of two Keyword objects.
Adding collection methods
Let’s get started. We know that we need a class that is a collection of strings so at first it might seem natural to let the Keywords class inherit from the generic List<string> and be done with it. However, as our class needs to be able to monitor if it has been modified, and as it needs to throw an exception when attempts are made to modify the collection while an instance is read-only we would need to override the methods that modify the collection. Unfortunately the methods for modifying the items in the generic List<T> class aren’t virtual so we can’t do that.
Instead we’ll settle for implementing the IEnumerable<string> interface so that it’s possible to iterate over the strings. We’ll also add a method for adding items to the collection later on. A first version of the Keywords class looks like this.
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using EPiServer.Core; public class Keywords : IEnumerable<string> { private List<string> values; public Keywords() { values = new List<string>(); } public IEnumerator<string> GetEnumerator() { return values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
As shown in the code above the Keywords class uses a generic list of strings internally to store the individual string in. The two GetEnumerator methods that are mandated by the IEnumerable<string> interface simply delegate to the internal list.
Instances of the Keywords class is now collections that can be iterated over but we also need a way to add keywords so we’ll also add an Add method. We’ll need to modify it later when we implement IReadOnly and a mechanism for keeping track of instances modification state but a first implementation looks like this.
public void Add(string keyword) { values.Add(keyword); }
Implementing IReadOnly
As mentioned in the previous post classes that are mutable, that is that can be changed after instantiation, and that are used as values for EPiServer properties should implement IReadOnly<T> where T is the class itself. The code necessary for doing that for the Keywords class is listed below. I’ve omitted members that we’ve already seen for brevity.
public class Keywords : IEnumerable<string>, IReadOnly<Keywords> { private bool isReadOnly; public Keywords CreateWritableClone() { var copy = new Keywords(); copy.values = new List<string>(values); copy.isReadOnly = false; return copy; } public void MakeReadOnly() { isReadOnly = true; } public bool IsReadOnly { get { return isReadOnly; } } }
The IReadOnly<T> defines three members that must be implemented. The two methods CreateWritableClone and MakeReadOnly and the property IsReadOnly. The CreateWritableClone method should return a copy of the object on which it’s invoked with the same value(s) but that isn’t read-only. The two latter members that must be implemented, MakeReadOnly and IsReadOnly are quite self-explanatory.
By implementing IReadOnly our class will have mechanisms for checking if the class is read-only, creating a non-read-only copy and making the class read-only. To accomplish this we use a private field, isReadOnly, for keeping track of the instance’s state. However, this doesn’t in it self mean that the class actually enforces that it can’t be changed when it has been made read-only. That’s something we’ll have to do in all methods that can modify our class. In Keywords’ case there's only one method that can modify instances of the class (except MakeReadOnly of course), the Add method.
public void Add(string keyword) { ThrowIfReadyOnly(); values.Add(keyword); } private void ThrowIfReadyOnly() { if (IsReadOnly) throw new NotSupportedException( "The list of keywords is read-only."); }
Enforcing read-only in a method is as simple as checking if the instance is read-only and if it is throwing an exception. To make the code more readable and to make the logic reusable in the likely event that we’d like to add more methods that can modify the class later we create a separate method for doing that which we let the Add method call before adding a new keyword to the collection.
Keeping track of modification state
As also described in the previous post a property must be able to tell whether it, including its value, has been modified. When the value is a mutable reference type it can be changed without setting the property’s Value property. Therefore the property must either keep track of its original value somehow so that it can compare its current value to that or the class that is used as value must keep track of its modification state and expose it so that the property class can ask for it. In Keyword’s case we’ll go with the latter alternative and add a IsModified property to the class.
private bool isModified; public bool IsModified { get { return isModified; } set { ThrowIfReadyOnly(); isModified = value; } }
The property is straight forward but it’s important to note that as the setter can change the state of an instance we should throw an exception if it is set while the instance is read-only.
We must also modify all members that can modify instances, or rather the value of instances (we don’t care about if it is made read-only), to set the instance’s modification state to modified when invoked. In Keyword’s case that means adding a line of code to the Add method.
public void Add(string keyword) { ThrowIfReadyOnly(); isModified = true; values.Add(keyword); }
Adding a Copy method
The PropertyData class has a method named Copy which is used when a page that the property is connected to is copied, something many editors like to do to save time. Since our property has a mutable reference type as value we must ensure that when a copy is created its value isn’t a reference to the same object as the old property. To do so we need to override the Copy method in our property class which we haven’t begun creating yet. However, to make it easy to copy instances of the Keywords class we’ll add a Copy method to it which we can later use.
public Keywords Copy() { var copy = (Keywords) MemberwiseClone(); copy.values = new List<string>(values); return copy; }
The interesting part in the above method is the second line where we assign a new list of strings to the copy’s value field. While the first line using MemberwiseClone produces a copy of the object the list of keywords (the values field) remains the same as it’s a reference. This is the case because MemberwiseClone only produces a shallow copy of the object it’s invoked on. So in order for the Copy method to produce a “deep” copy we need to ensure that it’s list of keywords is also a new object.
Overriding Equals
While it isn’t necessary I think it’s a good idea to override the Equals method and implement it so that it checks for value equality rather than reference equality (which the default implementation does). Being able to easily check for value equality usually comes in handy when writing tests and it is also a nice service to provide to users of reusable components, such as this property.
public override bool Equals(object obj) { var compareTo = obj as Keywords; if (compareTo == null) return false; return values.SequenceEqual(compareTo.values); }
Serialization and de-serialization methods
As mentioned before the ToString method needs to produce a string representation of a Keywords object from which the property class’ ParseToSelf and ParseToObject can recreate the object. This means that we need to place logic to serialize Keywords in the Keywords class and logic to de-seralize Keywords in the property class. I’m not particularly fond of scattering the serialization logic like that so what we’ll do is create a static method named Parse in the Keywords class that the property class can use.
We can serialize the value of our property, the Keywords class, to a string using whatever technique we want. We can for instance use XML-serialization or JSON-serialization. We can also create our own serialization algorithm, for instance separating values with semi-colon. In our case we’ll serialize the Keywords class to a string where the individual keywords are separated with commas.
The below code shows our implementations of the ToString and Parse methods. The CommaSeparatedStringHelper class that is used by both methods is a simple utility class for merging a collection of strings to a single string as well as reversing the procedure. You’ll find the code for it in the downloadable source code.
public override string ToString() { return CommaSeparatedStringHelper.Concatenate(values); } public static Keywords Parse(string value) { if(value == null) throw new ArgumentException( "Cannot parse Keywords objects from null", "value"); var result = new Keywords(); var keywords = CommaSeparatedStringHelper.Parse(value); result.values.AddRange(keywords); return result; }
The ToString method is simple as all it has to do is delegate to the utility class that takes care of serializing the list of keywords.
The Parse begins by checking if it is invoked with null. If it is it throws an ArgumentException. Otherwise it returns a new instance of Keywords that is populated with values de-serialized using the utility class.
Creating the property class
Our next task is to create the actual property which we’ll name PropertyKeywords. The first thing we need to do is decide which class it should inherit from. As all properties it must be a subclass of EPiServer’s PropertyData class but we certainly have an option to let our property inherit from another descendant of PropertyData. Since we’re going to store its value as a string one interesting alternative would be to let it inherit from PropertyString or PropertyLongString. By doing so we wouldn’t have to implement all of PropertyData’s abstract methods (described in the previous post) ourselves. On the other hand we would need to modify some of the parent class’ implementations to fit our needs. In the case of the Keywords class it really isn’t a string even though it's serialized to and saved as such. If our property were to inherit from PropertyString or some other built in property type we might be able to save a few lines of code but the code that we did write would be much harder to understand. So, PropertyKeywords will inherit directly from PropertyData.
Having decided which class to inherit from we can summarize what we’ll need to do to implement the property.
- Create a class that inherits from PropertyData and that has a PageDefinitionTypePlugIn attribute.
- Implement the six abstract members that PropertyData defines. These are the methods ParseToObject, ParseToSelf and SetDefaultValue as well as the properties PropertyValueType, Type and Value.
- Override the SaveData method to return a serialized version of the property’s value.
- Re-implement the CreateWritableClone and Copy methods to ensure that the value is also cloned or copied.
- Override the IsNull property and make it return true for empty collections of keywords.
- Re-implement the IsModified property so that it also checks if the Keywords object it has as value has been modified.
Creating the class
We begin creating the property class by letting it inherit from PropertyData and by adding a PageDefinitionTypePlugIn attribute to it. Adding the attribute isn’t mandatory but if we don’t users of our property need to manually configure it in EPiServer’s admin mode.
The code for this first step is shown below. At the moment it wont even compile as we haven’t implemented PropertyData’s abstract methods but we’ll get to that shortly.
using System; using System.Linq; using EPiServer.Core; using EPiServer.PlugIn; [PageDefinitionTypePlugIn(DisplayName = "Keywords")] public class PropertyKeywords : PropertyData { }
Storing the value
Before we begin implementing the abstract members that PropertyData defines we’ll add a backing field for the property’s value as well as a couple of constructors that initialize the field.
private Keywords keywords; public PropertyKeywords() : this(new Keywords()) {} public PropertyKeywords(Keywords keywords) { this.keywords = keywords; if(this.keywords == null) this.keywords = new Keywords(); }
In both constructors we initialize the field that holds the property’s value (named keywords) to either a value provided as a parameter or to a new, empty instance of the Keywords class. Should a caller call the second constructor with null as the value we instead set the field’s value to a new Keywords object. This, together with code in the Value property that we’ll look at soon ensures that the keywords field never is null saving us from having to do null checks all over the rest of the code in the property. This is a common pattern for EPiServer properties whose value is a collection.
Creating a strongly typed property for accessing the value
As mentioned in the previous post it is a common pattern to add a (code) property of the value’s type for getting and setting the value. This provides strongly typed access to the value for clients of the property who are aware of the property’s type, such as the property control. It also provides a place to put logic for validating the value when it is being set. That could however also be put in the Value property’s setter but this way that can focus entirely on handling different types of objects that it can be invoked with.
The code for our strongly typed property is shown below. Following the pattern from many of EPiServer’s own properties we give it the same name as its type, Keywords.
public Keywords Keywords { get { return keywords; } set { ThrowIfReadOnly(); if (value == null || value.Count() == 0) { Clear(); return; } keywords = value; Modified(); } }
The getter simply returns the backing field. The setter is a bit more interesting. As calling it will modify the instance and as the property class, via PropertyData, implements IReadOnly, we must ensure that it throws an exception if a client tries to change the value of a read-only instance. To accomplish that we call ThrowIfReadOnly which is a utility method provided by the base class, PropertyData.
The setter then proceeds to check if the value being set either is null or an empty collection. If it is it invokes another utility method in PropertyData, Clear. Clear will, among other things handle throwing an exception if the property is required. It will also set the property as modified, set the backing field used by the default implementation of IsNull to true and set the property’s value to the default by calling the SetDefaultValue method.
Finally, if the property isn’t read-only and if the value being set isn’t null or an empty collection it assigns it to the backing field (named keywords) and calls the Modified method. Modified is yet another helper method provided by PropertyData that will set the property’s modification status to true (modified).
Implementing the Value property
With a backing field created for the property’s value as well as the (optional) strongly typed property that wraps it we’re ready to create the six abstract members that inheriting from PropertyData demands. We’ll begin with the Value property. The Value property is fairly complex due to the fact that its setter will need to handle four different scenarios: when it is being set to null, when it is being set to a Keywords object, when it is being set to a string that can be de-serialized to a Keywords object and when it is being set to an object of an invalid type. Let’s look at the code for the property.
public override object Value { get { if (IsNull) return null; return keywords; } set { SetPropertyValue(value, () => { if (value is Keywords) { keywords = (Keywords) value; } else if (value is string) { ParseToSelf((string)value); } else { throw new ArgumentException( "Passed object must be of type Keywords or " + "a string that can be de-serialized to a " + "Keywords object."); } }); } }
The getter is pretty straight forward. It either returns null if the IsNull property, which we’ll soon create our own implementation of, is true. Otherwise it returns the value of the backing field for the value.
The setter on the other hand is more complex. It calls PropertyData’s SetPropertyValue method which expects two parameters, the value of type Object and a delegate that should be used to set the value. We supply the value by simply passing along the value that the property is being set with. As for the delegate we provide it using a lambda inside the call to SetPropertyValue.
By using the SetPropertyValue method we get some basic exception handling when our own logic for setting the value, the delegate, is invoked. SetPropertyValue will also handle the scenario when the value is null.
We begin the delegate’s implementation with a check if the value parameter is a Keywords object. If it is we assign it to the Keywords property for further validation before it is finally assigned to the backing field.
If the parameter isn’t a Keywords object we proceed to check if it is a string. If it is we pass it along to to the ParseToSelf method which will try to parse a Keywords object from the string and assign that to the property as value, as we’ll see soon.
Finally, if the parameter isn’t either a Keywords object or a string we throw an exception of type ArgumentException to which we attach an informative error message.
It should be noted that our delegate for setting the property’s value doesn’t explicitly handle the case where the value is null. This is because it will never be called with null as SetPropertyValue handles that scenario.
Implementing the ParseToSelf method
The ParseToSelf method should accept a string representation of the property’s value and assign it as value to the property instance that it is being invoked on. As we have created a method for parsing Keywords objects in the Keywords class itself our implementation of the ParseToSelf method is very straight forward.
public override void ParseToSelf(string value) { Value = ParseKeywords(value); } private Keywords ParseKeywords(string value) { if (QualifyAsNullString(value)) return new Keywords(); return Keywords.Parse(value); }
The method parses the string and assigns the result to the Value property. The ParseToSelf method doesn’t actually do any parsing itself but delegates to a private method named ParseKeywords. That method handles if the string represents a null value for the property (it is null or empty) using PropertyData’s QualifyAsNullString method. If it is it returns a new, empty Keywords object. Otherwise it simply delegates to Keyword’s static Parse method. As we’ll soon see we’ll also use the ParseKeywords method in our implementation of the ParseToObject method.
Note that while we handle cases where it is called with null or an empty string it is often also a good idea to handle scenarios where the ParseToSelf method is called with a non-empty but invalid string and throw an appropriate exception. However, since we’re dealing with comma separated strings in this example there really isn’t many any such cases.
Implementing the ParseToObject method
The ParseToObject should be implemented in a similar way as ParseToSelf but instead of assigning the parsed Keywords object to the property it is being invoked on it should return a new property with the parsed object as value.
public override PropertyData ParseToObject(string value) { Keywords parsedKeywords = ParseKeywords(value); return new PropertyKeywords(parsedKeywords); }
Implementing the SetDefaultValue method
The SetDefaultValue method should when implemented set the property’s value to, you guessed it, whatever the default should be. In our case that’s an empty Keywords object. Since this method can be used to modify the instance it’s invoked on we need to ensure that it throws an exception if it’s invoked on an instance that is read-only. We (again) accomplish that by calling PropertyData’s utility method ThrowIfReadOnly.
protected override void SetDefaultValue() { ThrowIfReadOnly(); keywords = new Keywords(); }
Implementing the PropertyValueType property
The PropertyValueType property is intended to return the type of the value, in our case Keywords.
public override Type PropertyValueType { get { return typeof(Keywords); } }The PropertyValueType may at first seem unnecessary but it is used for validation in EPiServer’s PropertyDataCollection class which has a generic method for retrieving the value of a property.
Implementing the Type property
The Type property should be implemented to return a value from the PropertyDataType enum. The PropertyDataType enum defines the different types that a property’s value can be stored as. In the case of PropertyKeywords we want to store it’s value as a string.
public override PropertyDataType Type { get { return PropertyDataType.String; } }
Overriding the SaveData method
The SaveData method should return the value of the property in the form that it should be saved as. In other words, it should return an object that represents the value but that is of the type that the Type property (previously described) returns. In many cases where the value either is of the type that it should be saved as or can be converted to such this means that SaveData can simply return the Value property and be done with it. In fact that’s just what its default implementation in PropertyData does. However, for properties that have a more complex type as value the SaveData method needs to be re-implemented to convert from the complex type of the value to the more simple type it is going to be saved as. In the case of PropertyKeywords this means returning the value serialized to a string.
public override object SaveData(PropertyDataCollection properties) { return keywords.ToString(); }
Overriding the IsNull property
The IsNull property defined in PropertyData should return true if the value of the property isn’t set by the editor. That isn’t necessarily the same thing as the property’s value being null internally. In fact, in PropertyKeywords case the backing field for the value, keywords, is never null. Instead if the editor hasn’t entered any keywords the backing field will be an empty Keywords object. Therefore we override the IsNull property and make it return true if there are no keywords (string) in the Keywords object that is the property’s value.
public override bool IsNull { get { return keywords.Count() == 0; } }
Overriding the IsModified property
The IsModified property is used by EPiServer, among other things, to check if the property’s value should be saved when a new version of the page it is attached to is saved. As we’ve previously discussed PropertyKeywords has a mutable reference type as value and it must therefore not only keep track of changes made to instances of itself but also to its value. We prepared for this when we added the IsModified property to the Keywords class. Left to do is to override the property class’ IsModified property.
public override bool IsModified { get { return base.IsModified || keywords.IsModified; } set { ThrowIfReadOnly(); keywords.IsModified = value; base.IsModified = value; } }
In the getter we return true if either the property has been modified, as indicated by it’s base class’ IsModified property or if the value has been modified.
We begin the setter with a call to the ThrowIfReadOnly method as the setter can modify the instance. We then set the modification status of both the property and of the value.
Overriding the CreateWritableClone method
When a property has a mutable reference type as value we need to ensure that the value is also cloned when the property is cloned using the CreateWritableClone method. Otherwise we will at best end up getting exceptions when the property’s value is to be updated as the clone still refers to the same value as the original property, a value which is read-only.
public override PropertyData CreateWritableClone() { var clone = (PropertyKeywords) base.CreateWritableClone(); clone.keywords = keywords.CreateWritableClone(); return clone; }
The method first creates a clone of the property and then ensures that the clone’s value is also cloned.
Overriding the Copy method
As we discussed when we created the Keywords class PropertyData has a method named Copy which is used when a page that the property is attached to is being copied. When the property has a mutable reference type as value we must also ensure that the value is also copied or we will otherwise end up with two properties with the same value in the web server’s memory. We do this by overriding the copy method.
public override PropertyData Copy() { var copy = (PropertyKeywords) base.Copy(); copy.keywords = keywords.Copy(); return copy; }
The method first creates a copy of the property by calling the base class’ Copy method. It then, before returning the copy, assigns a value of the original property’s value to the copy’s keywords field.
Overriding the CreatePropertyControl method
The CreatePropertyControl should return an instance of a class that implements the IPropertyControl interface. This instance is used both in EPiServer’s edit mode where it is used for editing the property’s value and in view mode (when viewing the site publically) when the property is rendered using the Property web control (EPiServer:Property).
PropertyData provides as default implementation of the method that returns a PropertyStringControl, a control for editing the value in a text box in edit mode. While we to want a text box the value of PropertyKeyword isn’t a string so we’ll need to override the CreatePropertyControl method to return an instance of a custom control.
public override IPropertyControl CreatePropertyControl() { return new PropertyKeywordsControl(); }
Of course the class PropertyKeywordsControl doesn’t exist yet. But we’ll remedy that right away.
Creating the property control
Our property control, PropertyKeywordsControl, will display a basic text box in edit mode with which an editor can enter a list of comma separated keywords. EPiServer comes with an abstract class, PropertyTextBoxControlBase, that is very convenient for creating property controls that use a text box. So, we begin by creating a new class that inherits from PropertyKeywordsControl.
using EPiServer.Web.PropertyControls; public class PropertyKeywordsControl : PropertyTextBoxControlBase { }
Overriding ApplyEditChanges
Since our custom property’s value isn’t a string we’ll need to parse the text box’s value when the property and the page it resides on is being saved. To do that we override the ApplyEditChanges method.
public override void ApplyEditChanges() { var keywords = CommaSeparatedStringHelper.Parse(EditControl.Text); var propertyValue = new Keywords(); foreach (var keyword in keywords) { propertyValue.Add(keyword); } SetValue(propertyValue); }
We begin by parsing the text box’s value using the same utility class that we have used before. This gives us a list of string which we add to a new Keywords object. Finally we assign that Keywords object to the property that the control is used for by calling the SetValue method which the base class of PropertyTextBoxControlBase, PropertyDataControl, provides.
We could have shortened the ApplyEditChanges method with a couple of lines by using the Parse method in the Keywords class since that parses a comma separated list into a Keywords object. That would however have embedded our knowledge about the implementation details of the Parse method in our property control and if we later on wanted to change how Keywords objects are serialized we would be in trouble.
Overriding the SetupEditControls method
We have taken care of parsing the string value inputted by the editor to a Keywords object but we still need to handle the reverse scenario, populating the text box with the keywords separated by comma.
protected override void SetupEditControls() { EditControl.Text = string.Empty; var keywords = PropertyData.Value as Keywords; if(keywords != null) EditControl.Text = CommaSeparatedStringHelper.Concatenate(keywords); }
We begin by setting the text box’s (EditConrol) text to an empty string. Then, given that the property’s value isn’t null, we change the text box’s text to a string containing the keywords separated by comma.
Just as with our implementation of ApplyEditChanges we could have utilized our knowledge of the implementation details in the Keywords class and have used the ToString method on the value instead of using the helper class to build the string. In fact we could have skipped overriding the SetupEditControls method all together and the result would have been the same as PropertyTextBoxControl’s implementation of SetupEditControls would end using Keywords’ ToString method. Again though, that would have been pretty bad design from our end as we would have relied on the implementation of ToString.
Using the Property
And we’re done! For now :)
To use our new custom property we need to ensure that the compiled assembly is put into the bin folder of an EPiServer site. We can of course do this in a variety of ways including adding a reference to it in the web project or copying it to the web project’s bin folder.
Once the assembly is in the web projects bin folder we can add properties of type Keywords to page types using EPiServer’s admin mode or through code using Page Type Builder.
Sample code
You can download the sample project here.
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.
Similar articles
- How to create a custom EPiServer property
- Sweet EPiServer Templating with TypedPageList
- Working with Dynamic Properties and Page Type Builder
- Developing with Page Type Builder – Using Interfaces and Advanced Inheritance
- Help your editors with a smarter PageReference property
- Limiting content and page reference properties to values of a specific type in EPiServer CMS
- Custom routing for EPiServer content
- Developing with Page Type Builder – Advanced Property Access
My book
Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!
Comments
comments powered by Disqus