EPiServer 7 CMS Development (2014)
II. SITE DEVELOPMENT
Chapter 8. Properties
Many years ago, before I started working with EPiServer’s products, I was talking to a subcontractor working for the consulting company where I worked. His time working with us was drawing to an end and we talked about his next assignment; building an EPiServer site.
As EPiServer CMS was becoming increasingly popular I was curious about it. So I asked him what EPiServer development was like. I still remember his reply: “Well, they have their properties.”. At that point his answer didn’t make much sense to me. Later however, once I had built a few EPiServer sites myself it did.
Indeed, EPiServer CMS as a product and EPiServer development revolves around EPiServer’s property concept. In this chapter we’ll learn the basics of EPiServer’s property model, look at different types of properties that we can create as well as how we can customize how properties are edited.
What is a property?
In the previous chapter we learned that page types, as well as the more general concept of content types, define characteristics for individual content items. Content types are entities stored in EPiServer’s database and can be created and modified either by creating classes or through the user interface in EPiServer’s admin mode.
Individual properties in content types follow a similar pattern; while we’re able to create and modify properties through regular C# properties in code, it’s also possible to add new properties to content types, and modify settings for existing properties, in admin mode.
Editing and adding properties for a content type in admin mode.
This in turn means that a property on a content type is more than a C# property. It to is an entity stored in EPiServer’s database. Such entities are objects of a class named PropertyDefinition. A PropertyDefinition is a named connection between a content type and a property type. Property types are classes that inherit from EPiServer’s PropertyData class.
When a content item, such as a PageData object, is returned from EPiServer’s API it’s populated with, or rather has references to, a number of PropertyData objects. In other words, while we can think of a content type as the template for content items property definitions are templates for properties on content items. Each such property is a PropertyData object.
Relationship between “meta models” and “content data”.
The PropertyData class is the abstract base class for all EPiServer properties and concrete subtypes of it define:
· The value type of the property, such as string, XHtmlString or SomeCustomClass.
· How the value should be stored in the database.
· How to convert between the value type and the value stored in the database.
An instance of PropertyData encapsulates almost everything there is to an individual property, meaning that it knows:
· What type of property it is (by means of what type it itself is).
· What content item it belongs to.
· What the value, either specified by an editor or programmatically, of the property is.
· Whether it has been modified or not.
· Whether it’s required.
That’s quite a lot. In fact, in earlier versions of the CMS a PropertyData object was also responsible for knowing how it should be edited and how it should be rendered as part of page. In EPiServer 7 that’s not the case any more, although the PropertyData class still has members related to that for backward compatibility reasons.
If you’re feeling a bit confused by now that’s OK. EPiServer’s property model is quite complicated. Don’t worry though, while it’s good to know how things work under the hood we don’t have to think much about these concepts during regular site development. We’ll look into the property model in more detail later on in the book. For now, let’s focus on the relationship between the code we write and what happens in the CMS.
Properties terminology As you’ve no doubt already noticed, the word “property” is frequently used in conjunction with EPiServer development. We develop in C#, and C# classes and interfaces can have properties. Then the CMS also has a concept of properties in content types. These two are not the same, although there is a connection between properties C# classes that are used to create content types. This situation means that it can sometimes be confusing to know what the word “property” means. In one context it may refer to a member in a C# class and in another it may mean a property in a content type, stored in EPiServer’s database. To further add to the confusion the CMS also has a Web Forms web control named Property. In this book, as well as among developers used to working with EPiServer, properties in content types, stored in the database, are referred to as “EPiServer properties”, “page properties” or “content properties”. Properties in content type classes are referred to as “code properties”, “C# properties” or “class properties”. Sometimes there’s no point in distinguishing between the two types of properties as we discuss an EPiServer property created by a C# property. For instance, when we discuss the MainBody property that we’ve created earlier in the book in general terms and there’s no point in distinguishing between the C# property and the EPiServer property that it maps to. In such cases we simply refer to it as a “property”. The Web Forms control Property is often referred to as the “property control” or, in writing, EPiServer:Property. |
Backing types and property value retrieval
So far we’ve created properties by adding regular C# properties to page type classes, like this:
...
publicclassStandardPage : PageData
{
publicvirtual string MainIntro { get; set; }
publicvirtual XhtmlString MainBody { get; set; }
}
...
When EPiServer synchronizes content types during initialization it looks at the (C#) properties in content type classes. For each such property it looks at the value type of the property and tries to map that type to a suitable subclass of PropertyData that has the same, or a compatible, value type as the property. The synchronization then continues to create a PropertyDefinition for the property.
For our two properties in the StandardPage page type class above the synchronization will create two property definitions. The definition for the MainIntro property will map the property as a PropertyLongString while the definition for the MainBody property will map the property as aPropertyXhtmlString.
Once a page of this type has been created we can access the values of the properties by using the C# properties. However, we can also get a hold of the actual PropertyData objects which reside in a collection in the pages Property property, like this:
EPiServer.Core.PropertyData mainBodyProperty = currentPage.Property["MainBody"];
//The below boolean variable will be true
bool isXtml = mainBodyProperty is EPiServer.SpecializedProperties.PropertyXhtmlString;
Once we have a PropertyData object we can retrieve the value of the property through its Value property:
EPiServer.Core.PropertyData mainBodyProperty = currentPage.Property["MainBody"];
object mainBodyValue = mainBodyProperty.Value;
While the compiler has no way of knowing the type of the property the returned value is the same, or an equivalent, object as we would get if we used the C# MainBody property. In other words, we have two different ways of retrieving the value of the MainBody property:
var value1 = currentPage.Property["MainBody"].Value as XhtmlString;
var value2 = currentPage.MainBody;
bool theSame = value1.ToString().Equals(value2.ToString()); //True
In earlier versions of the CMS there wasn’t the concept of “typed pages” allowing us to define properties in page type classes. Therefore EPiServer offered, and still offers, another way of retrieving property values that requires less code than first retrieving the PropertyData object; using indexer syntax on a PageData object:
//Shortcut for currentPage.Property["MainBody"].Value
var value1 = currentPage["MainBody"] as XhtmlString;
var value2 = currentPage.MainBody;
bool theSame = value1.ToString().Equals(value2.ToString()); //Also True
So, there’s a difference between “EPiServer properties” and “code properties” and there are several ways to access the value of a property. But, why are we discussing this? Isn’t it enough to define properties as C# properties and access property values using the C# properties that have defined them? In many situations it is, but sometimes we may want or need to:
· Work with a site where properties aren’t defined using C# properties. For instance a site that has just been upgraded from older versions of the CMS.
· Iterate over all properties for a content item.
· Inspect other characteristics of the property than its value.
· Change the type of a property while maintaining the same value type for the C# property.
· Implement custom getters and setters for properties.
Custom getters and setters
So far all properties that we have created have had automatic, compiler generated, getters and setters. Like this:
publicvirtual string MainIntro { get; set; }
It doesn’t seem far fetched that EPiServer populates these properties by invoking their getters when serving a page from its API. That’s not the case however. Instead EPiServer, through the open source project Castle DynamicProxy, creates classes that inherit from our content type classes at runtime. In these classes EPiServer overrides properties that we have defined with automatic getters and setters to provide new getters and setters. Those getters and setters access the PropertyData object that maps to the C# property and retrieves, or sets, its Value property.
In other words, when a PageData object with a property, like the MainIntro property above, is returned from EPiServer’s API the property is overridden and the getters and setters implementations are swapped out. Instead of having compiler generated getters and setters that gets or sets the value from a (also compiler generated) private variable the property is implemented something like this:
publicvirtual string MainIntro
{
get { returnthis["MainIntro"] as string; }
set { this["MainIntro"] = value; }
}
While it’s certainly nice of EPiServer to take care of implementing automatic properties for us we’re free to provide implementations for getters and setters ourselves. In other words, if we modified our existing MainIntro property in the StandardPage class so that the code for it looked like the code above, EPiServer wouldn’t re-implement it and our getter and setter would be used.
This can come in handy in some situations, especially when we want a property to have a value even if an editor hasn’t specified one. For instance, in the previous chapter we created a base class for all page types containing two properties, Title and MetaDescription. Currently the value for both of these properties need to be explicitly set on a page, or otherwise the page’s title and meta description will be blank.
On the start page this is natural as the start page doesn’t hold anything that can be used in place of those properties. However, on pages of the standard page type the PageName and MainIntro properties would most likely be suitable to use if the Title and MetaDescription properties haven’t been explicitly set by an editor.
While we could implement this logic during rendering in the template, its place is really in the page type class. After all, this logic isn’t really rendering logic but rather business logic belonging to the domain model. Albeit the domain model here is the data for a page that should be rendered. Also, by placing such logic in the page type class we can reuse it across several templates.
So, let’s override the Title and MainIntro properties in the StandardPage class. Before we can do that though, given that we’re using version 7.0 of EPiServer CMS without any patches applied, we need to first implement the getters and setters for the same properties in the BasePage class. This is necessary due to EPiServer’s validation during synchronization of content types that will think that our overridden properties are compiler generated, even though they aren’t, and throw an exception as they aren’t marked as virtual.
So, we begin by modifying the BasePage class to use custom getters and setters for both its properties.
publicabstractclassBasePage : PageData
{
publicvirtual string Title
{
get { returnthis["Title"] as string; }
set { this["Title"] = value; }
}
publicvirtual string MetaDescription
{
get { returnthis["MetaDescription"] as string; }
set { this["MetaDescription"] = value; }
}
}
The above implementation of the properties does its job. However, we’re relying on (magic) string values to get and set the value of the underlying properties which is error prone. Luckily, EPiServer provides two extension methods for ContentData named GetPropertyValue andSetPropertyValue. When using these we provide expressions that the methods use to figure out the name for us in a strongly typed way. After using these the BasePage class looks like this:
publicabstractclassBasePage : PageData
{
publicvirtual string Title
{
get { returnthis.GetPropertyValue(x => x.Title); }
set { this.SetPropertyValue(x => x.Title, value); }
}
publicvirtual string MetaDescription
{
get { returnthis.GetPropertyValue(x => x.MetaDescription); }
set { this.SetPropertyValue(x => x.MetaDescription, value); }
}
}
We’re now ready to override the Title and MetaDescription property in the StandarPage class to achieve the fall back behavior, making it look like this:
//Attributes omitted for brevity
publicclassStandardPage : BasePage
{
publicoverride string Title
{
get
{
var title = base.Title;
if (string.IsNullOrEmpty(title))
{
title = PageName;
}
return title;
}
set
{
base.Title = value;
}
}
publicoverride string MetaDescription
{
get
{
var metaDescription = base.MetaDescription;
if (string.IsNullOrEmpty(metaDescription))
{
metaDescription = MainIntro;
}
return metaDescription;
}
set
{
base.MetaDescription = value;
}
}
publicvirtual string MainIntro { get; set; }
publicvirtual XhtmlString MainBody { get; set; }
}
In both of the properties getters we’re first using the getter from the base class to retrieve the value of the property. We then proceed to check if the value is empty. If it is we instead return the value of the PageName property or the MainIntro property.
There are other ways of accomplishing the same goal. For instance, we could have used an overload of the GetPropertyValue method that retrieves either the property’s value or a specific default value, like this:
get
{
returnthis.GetPropertyValue(x => x.Title, PageName);
}
This would have achieved the same result in our specific case. However, by using the base class’ getter we make it possible to change the base class’ getter and have that change reflected in the StandarPage class, which may be a good thing.
Anyhow, while editors can control the title and meta description explicitly for pages of the standard page type our custom getters and setters will provide sensible defaults if they haven’t. The images show this in action for the MetaDescription property on the “About Fruit Corp” page.
The page viewed in forms editing mode along with the publicly rendered HTML when the MetaDescription property hasn’t been populated.
The same views of the page when the MetaDescription property has been populated.
When we’re creating custom getters and setters for properties we’re free to add any logic we want in them. For instance, we could modify the getter for the MetaDescription property to only return the first 100 or so characters from the MainIntro property should we want to. |
The Display attribute
As with content types themselves individual properties in content types have a number of settings. And, as with content types these settings for properties can be modified from EPiServer’s admin mode and default values can be provided in code, using attributes. Settings related to how a property is presented for editors are controlled using an attribute named Display, located in the namespace System.ComponentModel.DataAnnotations.
As the attribute isn’t an EPiServer specific class but provided by the .NET framework not all of its properties are relevant during EPiServer development. The properties that are relevant are:
Name |
Type |
Default value |
Description |
string |
null |
GroupName |
string |
null |
Name |
string |
null |
Order |
int |
0 |
Name and Description
The Name and Description properties map to the “Field name” and “Help text” settings in admin mode. The Name property can be used to provide the default edit caption, the name of the property when displayed in edit mode, for the property. The Description property can be used to provide a default help text that is displayed when hovering with the mouse pointer over the property’s name in forms editing mode.
As an example, we could use the Display attribute to specify the edit caption and help text for our MetaDescription property like this:
...
[Display(
Name = "Meta description",
Description = "Short description of the page used by search engines.")]
publicvirtual string MetaDescription
...
Sometimes it may not be obvious to editors what a property is intended for, especially for properties that aren’t editable during On Page Editing such as our Title and MetaDescription properties. Therefore providing good edit captions and help texts for properties can greatly improve the user experience for editors. However, as with the similar settings for content types these settings can also be specified using EPiServer’s localization feature, by default using “language files”, which is often a better approach.
Order
The Order property is used to provide the default sort order for the property and maps to the “Sort index” setting in admin mode. The sort order of the properties on a content type determines in what order they appear in forms editing mode as well as when they are listed in admin mode. We could for instance make the MainBody property appear before the MainIntro property in forms editing mode like this:
...
[Display(Order = 2)]
publicvirtual string MainIntro { get; set; }
[Display(Order = 1)]
publicvirtual XhtmlString MainBody { get; set; }
...
When properties lack a Display attribute with Order specified they are sorted by the order in which they appear in the class. However, note that this is only true when the properties are first created, meaning that adding a new property to the top of an existing content type class doesn’t make it appear at the top in admin mode. For this reason it’s a good idea to specify Order for properties even if they appear in the desired order after creating a new content type. It’s also a good idea to use a larger interval for the values in case we later need to insert a new property between two existing ones.
Let’s apply the above advice to ensure that the MainIntro property appears before the MainBody property in pages of our StandardPage type.
Specifying Order for properties in StandardPage.cs.
...
[Display(Order = 10)]
publicvirtual string MainIntro { get; set; }
[Display(Order = 20)]
publicvirtual XhtmlString MainBody { get; set; }
...
Note that by setting Order to 10 for the MainIntro property and 20 for the MainBody property we can easily add a new property between the two existing ones simply by setting Order for the new property to a number between 10 and 20.
GroupName
The GroupName property maps to the “Tab” setting for properties in admin mode. Using this property we can control which tab the property is displayed on in forms editing mode. If the value specified matches an existing tab the property will be placed on that tab, otherwise a new tab will be created. The names of the predefined tabs in a new EPiServer CMS installation are exposed as constants in the EPiServer.DataAbstraction.SystemTabNames class.
As an example, the below code would move the Title property from the default tab, the “Content” tab, to the “Settings” tab.
usingEPiServer.DataAbstraction;
...
[Display(GroupName = SystemTabNames.Settings)]
publicvirtual string Title
...
On our site moving the Title property to the “Settings” tab wouldn’t make much sense. Instead it would be good to move the two meta data properties to their own tab, making it easier for editors to distinguish between “meta properties” and “content properties”. In order to do so we’ll need to modify the BasePage class as well as the StandardPage class, where the two meta data properties are overridden.
Specifying GroupName (and Order) for Title and MetaDescription in BasePage.cs.
usingSystem.ComponentModel.DataAnnotations; //In the top of the class
...
[Display(GroupName = "Meta data", Order = 10)]
publicvirtual string Title
{
get { returnthis.GetPropertyValue(x => x.Title); }
set { this.SetPropertyValue(x => x.Title, value); }
}
[Display(GroupName = "Meta data", Order = 20)]
publicvirtual string MetaDescription
{
get { returnthis.GetPropertyValue(x => x.MetaDescription); }
set { this.SetPropertyValue(x => x.MetaDescription, value); }
}
...
Specifying GroupName (and Order) for Title and MetaDescription in StandardPage.cs.
usingSystem.ComponentModel.DataAnnotations; //In the top of the class
...
[Display(GroupName = "Meta data", Order = 10)]
publicoverride string Title
[Display(GroupName = "Meta data", Order = 20)]
publicoverride string MetaDescription
...
The two meta data properties displayed on their own tab in forms editing mode.
Localization
In the previous chapter we learned how to specify the name and description for content types using EPiServer’s localization feature. Using the same approach we can also provide friendly captions and help texts for properties. The CMS tries to find captions and help texts for properties using the below XPath expressions:
· /pagetypes/pagetype[@name={page_type_name}]/property[@name={property_name}]/caption
· /pagetypes/pagetype[@name={page_type_name}]/property[@name={property_name}]/help
This allows us to specify the caption and help text for a specific property on a specific content type in each language we choose to create translations for. As an example, we could create a new language file and provide English translations for the StandardPage.MainIntro property with the following XML structure.
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<pagetypes>
<pagetype name="StandardPage">
<property name="MainIntro">
<caption>Preamble</caption>
<help>Short, descriptive introduction text.</help>
</property>
</pagetype>
</pagetypes>
</language>
</languages>
We’re however not forced to translate properties in a separate file and often it’s more convenient to put property translations together with the rest of the translations for our content types. So, to provide friendly captions and help texts for the MainIntro and MainBody properties on ourStandardPage page type let’s open up the language file we created in the previous chapter, lang/ContentTypesEN.xml. There we locate the element matching the page type and modify it like this:
<pagetype name="StandardPage">
<name>Standard page</name>
<description>
Used for standard editorial content pages.
</description>
<property name="MainIntro">
<caption>Preamble</caption>
<help>Short, descriptive introduction text.</help>
</property>
<property name="MainBody">
<caption>Body text</caption>
<help>The page's main content.</help>
</property>
</pagetype>
That takes care of the two properties that are unique to standard pages, but we should also provide friendly captions and help texts for the Title and MetaDescription properties that both of our two page types have. We could of course use the same approach as above for these, translating them for each page type. Alternatively, and more conveniently when dealing with properties with the same name and usage on multiple content types, we can provide translations for properties in a way that isn’t specific to a single content type. In order to do that we can put such property translations in a common element inside a pagetypes element. On it’s own in a language file the XML structure looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<pagetypes>
<common>
<property name="MetaDescription">
<caption>Meta description</caption>
<help>Short description of the page used by search engines.</help>
</property>
</common>
</pagetypes>
</language>
</languages>
Let’s apply this technique to our language file, ContentTypesEN.xml, and provide translations for both Title and MetaDescription. After doing so the entire file should look like this:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<pagetypes>
<!-- Page types and properties specific to them -->
<pagetype name="StartPage">
<name>Start page</name>
<description>
Used for the site's start page.
</description>
</pagetype>
<pagetype name="StandardPage">
<name>Standard page</name>
<description>
Used for standard editorial content pages.
</description>
<property name="MainIntro">
<caption>Preamble</caption>
<help>Short, descriptive introduction text.</help>
</property>
<property name="MainBody">
<caption>Body text</caption>
<help>The page's main content.</help>
</property>
</pagetype>
<!-- Common properties -->
<common>
<property name="Title">
<caption>Title</caption>
<help>The page's title in the browsers title bar. Also used by search engines\
.</help>
</property>
<property name="MetaDescription">
<caption>Meta description</caption>
<help>Short description of the page used by search engines.</help>
</property>
</common>
<!-- Groups -->
<groups>
<group name="Editorial">Editorial</group>
<group name="Specialized">Specialized</group>
</groups>
</pagetypes>
</language>
</languages>
The UIHint attribute
Having moved the Title and MetaDescription properties to their own tab in forms editing mode and with user friendly captions and help texts in place for our properties we’ve drastically improved the user experience for editors working with the content on our site. However, in terms of user experience for editors there’s a problem with the MetaDescription and MainIntro properties. Both these properties are intended for fairly short texts, but not so short texts that it’s suitable to edit them with in a small textbox. A textarea would be much better for these properties.
In situations such as this, when the value type of a property is what we want but we’d like the property to be edited in a different way we can instruct the CMS to use a different “editor”. While there are many ways of doing that the most common is to use the UIHint attribute from theSystem.ComponentModel.DataAnnotations name space. Used in its simplest form the UIHint attribute requires a string value, a “UI hint”. When the CMS encounters a UIHint attribute on a property it will look for an editor associated with the UI hint and if such an editor exists it will use that when the property is edited.
In order to make the MetaDescription property on the start page edited using a textarea we can add a “textarea” UI hint to it in BasePage.cs, like this:
[UIHint("textarea")]
publicvirtual string MetaDescription
The “textarea” UI hint matches an editor that comes out-of-the-box with the CMS and after compiling the we can see that the property is now indeed presented to editors using a textarea.
As the “textarea” UI hint maps to an editor provided by the CMS that string, along with a few other common UI hints, are provided as constants in the class EPiServer.Web.UIHint. This means that we can modify the attributes usage in BasePage.cs to use the constants value instead of a “magic string”. After doing so BasePage.cs should look like this:
usingEPiServer.Core;
usingEPiServer.Web;
usingSystem.ComponentModel.DataAnnotations;
namespaceFruitCorp.Web.Models.Pages
{
publicabstractclassBasePage : PageData
{
[Display(GroupName = "Meta data", Order = 10)]
publicvirtual string Title
{
get { returnthis.GetPropertyValue(x => x.Title); }
set { this.SetPropertyValue(x => x.Title, value); }
}
[Display(GroupName = "Meta data", Order = 20)]
[UIHint(UIHint.Textarea)]
publicvirtual string MetaDescription
{
get { returnthis.GetPropertyValue(x => x.MetaDescription); }
set { this.SetPropertyValue(x => x.MetaDescription, value); }
}
}
}
Let’s also make the MetaDescription and MainIntro properties in StandardPage.cs edited using a textarea by modifying the class like this:
usingEPiServer.Core;
usingEPiServer.DataAnnotations;
usingEPiServer.Web;
usingSystem.ComponentModel.DataAnnotations;
...
[Display(GroupName = "Meta data", Order = 20)]
[UIHint(UIHint.Textarea)]
publicoverride string MetaDescription
...
[Display(Order = 10)]
[UIHint(UIHint.Textarea)]
publicvirtual string MainIntro { get; set; }
...
Property attributes
So far we’ve looked at two attributes that we can use to annotate properties in content types, Display and UIHint. While these two are perhaps the most interesting ones there are a number of other attributes that we can use to customize properties in various ways. Many relate to validation of properties which we’ll look at later, but we’ll now briefly discuss those that map to settings in admin mode.
The CultureSpecific attribute
While we haven’t yet looked at multi-lingual sites, the CMS supports representing the same content in multiple languages. That is, a single page can exist in several language versions where editors can enter unique content for each language. When utilizing this feature each property’s value can either have a unique value per language or a single value, fetched from the master language.
By default the latter is true. This means that if we were to configure our site to also have Swedish content editors would be able to publish Swedish versions of the existing pages but not enter Swedish-specific content for properties such as MainIntro and MainBody. To change that for theMainIntro property we could change the “Unique value per language” setting for the property in admin mode. Or, we could provide a default value for the setting using the CultureSpecific attribute (located in EPiServer.DataAnnotations), like this:
[CultureSpecific(true)]
publicvirtual string MainIntro { get; set; }
The ScaffoldColumn attribute
Sometimes we may create properties that are only meant to be modified by the application it self, through our code, and not by editors. In other situations we may want to remove a property but find that we can’t do that just yet as some code that has yet to be rewritten relies on it.
In these cases we can hide the property from editors by unchecking the “Display in Edit Mode” checkbox for the property in admin mode. Alternatively we can accomplish the same result by annotating the property with a ScaffoldColumn attribute, from theSystem.ComponentModel.DataAnnotations name space. If you find the name of the attribute somewhat confusing, know this: you’re not alone :)
Below is an example of how we could hide the MainIntro property from edit mode. Note that this setting both hides the property from forms editing mode and ensures that there’s no blue border around the property in On Page Editing mode.
[ScaffoldColumn(false)]
publicvirtual string MainIntro { get; set; }
The Searchable attribute
The Searchable attribute found in the EPiServer.DataAnnotations name space allows us to provide default values for the “Searchable property” setting for properties found in admin mode. This setting controls whether the propertys value will be indexed by EPiServer Full Text Search, the basic free text search functionality that ships with the CMS. It is also used by EPiServer’s more advanced search product Find.
If a property isn’t annotated with the Searchable attribute the setting will default to true for string properties and false for all other properties.
The BackingType attribute
As we previously discussed, during synchronization of content types and their properties EPiServer maps from the value type of a C# property to an EPiServer property type, meaning a type inheriting from PropertyData. In most cases this works well. However, in some scenarios we want to control this mapping ourselves.
In order to do that we can use EPiServer’s BackingType attribute. As an example, imagine that we want to create a string property but have editors edit it as a number and make EPiServer store it as a number in the database. In order to do that we could create a string property and annotate it with [BackingType(typeof(PropertyNumber))] and then handle converting between the actual property value and a string in the getter and setter. A crude implementation could look like this:
[BackingType(typeof(PropertyNumber))]
publicvirtual string NumberString
{
get { return (this["NumberString"] as int?).ToString(); }
set { this["NumberString"] = int.Parse(value); }
}
The Ignore attribute
By default the CMS tries to create “EPiServer properties” for all C# properties in content type classes which have both a getter and a setter. Sometimes we may want to add properties to content type classes for which no property in the CMS should be created.
In most situations this isn’t a problem as such properties tend to not need a setter. However, should we need to create a property with both a getter and a setter that we don’t want a corresponding CMS property for we can annotate it with the Ignore attribute, from theEPiServer.DataAnnotations name space.
Default values
It’s possible to provide default values for properties in two different ways. One is through admin mode.
Using the Default value setting in admin mode it’s possible to specify a default value for the property. It’s also possible to specify that the default value should be an inherited value, meaning that the property will be populated with the same value as the corresponding property in the pages parent.
It’s also possible to specify default values for properties through code. To do that we can override the SetDefaultValues method in a content type class and set values of one or more properties. Below is an example of how we could set a default value for our MainIntro property using this approach:
...
usingEPiServer.DataAbstraction;
namespaceFruitCorp.Web.Models.Pages
{
[ContentType(
GUID = "1cfe38ca-025e-4f66-a237-226c44f586e9",
GroupName = "Editorial")]
[ImageUrl("~/Content/Icons/Standard.png")]
[AvailablePageTypes(Include = new[] { typeof(StandardPage) })]
publicclassStandardPage : BasePage
{
publicoverridevoid SetDefaultValues(ContentType contentType)
{
base.SetDefaultValues(contentType);
MainIntro = "Default preamble. Change me.";
}
...
}
}
Property types
In this and previous chapters we’ve looked at properties of type string and XHtmlString and discussed that C# properties map to objects of type PropertyData. The type of those “EPiServer properties” is referred to as the “backing type” and that type can be explicitly set using the BackingTypeattribute.
Apart from the property types that we’ve used so far, strings with the default backing type PropertyLongString and XHTML strings with the backing type PropetyXhtmlString, EPiServer ships with support for a number of other property types.
Boolean values
C# properties of type bool or bool? map to EPiServer properties with backing type PropertyBoolean. Such properties are edited using a simple check box.
Perhaps somewhat confusingly EPiServer stores the value false as null in the database. This means that a property of type bool that doesn’t have true as value will have the value false while a property of type bool? that doesn’t have true as value has null as value. In other words, a nullable boolean property can never have the value false.
When a bool property is rendered using the PropertyFor method it’s displayed as a disabled checkbox. When rendered using the EPiServer:Property control in a web forms context it’s either not shown at all or as the string “True” when the value of the property is true. Of course, there are hardly every any reason to render a boolean property.
Nullable Types
In C# types whose names end with a question mark, such as bool? above, are nullable types. These types are wrappers for value types that normally can’t have null as value. Instances of these types have a Value property through which the actual, wrapped, value can be retrieved. They also have a HasValue property that returns true if the object holds a wrapped value.
For more information about nullable types see http://msdn.microsoft.com/en-us/library/1t3y8s4s.aspx
Use nullable types for all but boolean properties Under the hood all EPiServer properties can be null. When a C# property’s type is a value type and the EPiServer property’s value is null, then the default value will be returned when retrieving the value from the C# property. This means that a C# property of type int has the value zero both if an editor has explicitly set it to zero and if the property hasn’t been given any value at all. Therefore I recommend you to always use the corresponding nullable types when creating properties of value types. That way it’s possible to distinguish between when the property doesn’t have any value at all and when it has explicitly been given a value that matches the types default value. There are two exceptions to this recommendation. One is for boolean properties. For these EPiServer doesn’t store the value false in the database meaning that a nullable bool property won’t ever have the value false. The second exception is when we truly want the default value for the type when an editor hasn’t given the property a value. |
Dates
Properties in content type classes of type DateTime or DateTime? are mapped to EPiServer properties of type PropertyDate. Date properties are edited using a date picker through which editors select a date from a calendar and can also enter a time.
When no value has been specified for a date property it will be null, meaning that a corresponding C# property of type DateTime? will also have the value null while a non-nullable DateTime property will have the value produced by default(DateTime).
A DateTime property rendered using the PropertyFor method produces the date and time as a string, the string returned by the property value’s ToString method. When rendered using the EPiServer:Property control it’s also shown as a string if the property has been set to a value, otherwise nothing is rendered.
Numbers
EPiServer can store numerical property values as one of two types in the database; int or float. These two database column types map to the backing types PropertyNumber and PropertyFloatNumber respectively.
C# properties of type int and int? map to PropertyNumber while both float, float?, double and double? map to PropertyFloatNumber. As with boolean and date values, non-nullable numerical properties have the default value for the type (0) when not set to something else while the nullable versions are null.
Both integer and floating point numerical properties are edited using a text box. For integer properties the text box is extended to be a “number spinner”, allowing editors to increment or decrement the value by clicking up or down arrows in addition to typing.
As with dates number properties are rendered as their string representations when the PropertyFor method is used. The same is true when EPiServer:Property is used, but only if the property has a value.
Strings
The CMS has two backing types for strings; PropertyString and PropertyLongString. The value of properties of the first type is stored in a column of type nvarchar(450) in the database and the class enforces a maximum length of 255 characters on the value. PropertyLongString supports strings of any length.
C# properties of type string maps to the backing type PropertyLongString. This means that PropertyString is rarely used and exists primarily for legacy reasons. However, back in the days of earlier versions of the CMS the PropertyString type was commonly used. In those days it was common to refer to this property type as “short strings” to distinguish them from “long strings”.
String properties are by default edited using a text box. However, as we’ve already seen it’s easy to change the editor to a text area for a string property by annotating it with [UIHint(UIHint.Textarea)].
XHTML strings
In addition to PropertyLongString and PropertyString EPiServers API also feature a specialized string type, XhtmlString. C# properties of this type are mapped to the backing type PropertyXhtmlString. The PropertyXhtmlString type inherits PropertyLongString and, as its base, type it supports strings of variable length. XhtmlString properties are edited using a rich text/WYSIWYG editor; TinyMCE.
XhtmlString objects can be created from regular strings but behind the scenes these objects don’t store a single string. Instead the original string value is parsed and split up into “fragments”, instances of classes implementing the EPiServer.Core.Html.StringParsing.IStringFragmentinterface. This intricacy is caused by, and enables, the fact that XhtmlString objects are far more advanced than regular strings.
One example of this is that the “value” of a XhtmlString may differ depending on context. The context may for instance be the CMS’ personalization functionality, meaning that the XhtmlString should have one value for some visitors to the site and a different value for other visitors.
As XhtmlString doesn’t inherit from string we can’t use XhtmlString objects as strings. Instead, to retrieve a regular string value from a XhtmlString we need to use its ToHtmlString method. Alternatively we can also use its ToString method which has been overridden to act as an alias forToHtmlString.
When the ToHtmlString method is used the string fragments are first filtered to exclude parts that shouldn’t be displayed to the current visitor and then merged to a single string value. In most situations that’s a good thing. However, in some situations we may want a user context agnostic representation of the XhtmlString.
One example of such a case may be when creating RSS feed functionality. Then we may want the same content in the feed no matter where the feed is requested from, even if editors have used personalization functionality to show special content to visitors from certain geographic regions. In such situations we can use an overload of the ToHtmlString method that requires a System.Security.Principal.IPrincipal object and pass it an object representing an unknown user, like this:
//using EPiServer.Security;
currentPage.MainBody.ToHtmlString(PrincipalInfo.AnonymousPrincipal);
Render XhtmlString properties using PropertyFor or EPiServer:Property As we’ve already seen to some degree XhtmlString objects are far more advanced than regular strings and editors can embed various types of functionality in them, such as personalization and server side logic using a concept called dynamic content. In order for some of this functionality to work properly it’s not just enough to render the return value of the ToString method invoked on a XHTML string. Instead the propertys value needs additional parsing and transformation before it’s rendered. The PropertyFor method and EPiServer:Property method takes care of this for us. Therefore we should always us PropertyFor to render XhtmlString properties when using ASP.NET MVC and the EPiServer:Property control when using Web Forms. Of course, as with all rules there are exceptions, such as when outputting the propertys value in an RSS feed, but this is a rule we should only break if we know what we’re doing. If we routinely render XHTML string properties by simply outputting them in templates we may not notice a problem but we have destroyed editors abilities to use some functionality in the CMS and possibly made upgrading harder. |
Property types, or backing types if you will, can be associated with a property settings class implementing an interface named IPropertySettings, found in the EPiServer.Core.PropertySettings name space. Property settings classes define on or more settings that can be associated with a specific property in a content type. These settings can be controlled in admin mode and allow those with access to admin mode to customize the property. The settings can either be set for each specific property or defined globally.
Most property types that ship with EPiServer CMS doesn’t have any such settings, but XHTML strings do. Or rather, the TinyMCE editor that is used as the default editor for XHTML string properties do. Using these settings we can control the size of the editor and what tools should be available in it. We can also enter the path to a CSS file from which specific CSS classes will be loaded in the editor. We’ll come back to the setting for the CSS file later in this chapter.
References to content
As we discussed in chapter 4 ,IContent objects, such as pages, are identified using objects of type ContentReference. We can add properties of type ContentReference to content type classes to store references to other content. Such properties will be mapped to the backing typePropertyContentReference.
In edit mode editors will be able to edit such properties using a “content picker”, a text box showing the name and ID of the currently selected content and a button that opens up a dialog in which a content item can be selected from the content tree.
The Custom Settings tab for the MainBody property in admin mode.
It’s also possible to create properties of the more specialized PageReference type in content type classes. Such properties will be mapped to the backing type PropertyPageReference. While similar to ContentReference properties PageReference properties are, by the compiler, limited to references to pages. They also have a slightly different editor that filters the dialog in which editors selects the content to link to so that it only displays pages.
ContentReference properties will be rendered as strings, in the form of what is returned by the values ToString method, when rendered using the PropertyFor method or the EPiServer:Property control. PageReference properties on the other hand are rendered in a much more useful way byPropertyFor and EPiServer:Property. They are rendered as anchor tags with the pages URL as the href attribute value and the pages name (the PageName property) as text.
URLs
EPiServers API contains a class named Url in the EPiServer name space. C# properties of this type in content type classes will be mapped to properties with backing type PropertyUrl. Hardly surprisingly, such properties can be used to store URLs. However, there won’t be any validation that the entered value is actually a valid URL. That’s because these properties are intended to be used to store anything that you can create a hyper link to and such a value may for instance be “mailto:…”.
By default, Url properties are edited using a text box and a button. Editors can type anything they want into the check box, or they can click the button to open up a dialog in which they get assistance to create a link to web pages, both internal and external, files, again both internal or external, as well as e-mail addresses.
When rendered using the PropertyFor method or the EPiServer:Property control Url properties are rendered as anchor tags with the URL as the href attribute value as well as the text. In most situations this isn’t very useful and the value of a Url property is typically rendered in some other way, such as the value for an img tag’s src attribute.
One of the most common usages for Url properties is for editors to populate them with the URL for an image or some other type of file that has been uploaded in the CMS. While the default editor for Url properties works for this the user experience isn’t optimal.
Therefore the CMS ships with functionality to limit the dialog to either images, video files or documents. To use this functionality we can annotate a Url property with an UIHint attribute and use one of the constants Image, Video or Document found in the EPiServer.Web.UIHint class.
Link collections
So far all of the property types that we’ve looked at only enables us to store a single value. However, sometimes we need to store multiple values. One property type that allows us to store multiple values, more precisely multiple links, is PropertyLinkCollection. A link in a link collection is comprised of a target, an URL or e-mail address, and either a text or an image that make up the visual part of the link.
Link collection properties can be created in content type classes by adding C# properties of type LinkItemCollection, found in the EPiServer.SpecializedProperties name space. Link item collections are edited using a pop up dialog in which new links can be added and existing links can be edited, deleted and sorted.
When rendered using the PropertyFor method or the EPiServer:Property control LinkItemCollections properties are outputted as unordered lists (ul/li). Each link will be rendered as an anchor tag in a list item.
Content areas
C# properties of type ContentArea map to the backing type PropertyContentArea. Content areas store a list of ContentReference objects. From a purely technical perspective content areas may not seem very exciting as we could store multiple references to content items in a link item collection as well. However, the functionality for editing and rendering content areas is far more advanced. In forms editing mode content areas are edited using a drop area in which editors can drop pages and other content from the page tree and other places.
An empty content area property in forms editing mode.
A content area property with a couple of pages in it in forms editing mode.
We’ll look at how to work with content areas and how they are rendered more extensively in later chapters.
XForms
XForms is a functionality built in to the CMS through which editors can create forms using a graphical interface. A form is made up of various input elements and buttons positioned in a table. Editors can configure XForms to store submitted form values in the database, have them sent in e-mails or posted to an URL.
C# properties of type XForm map to the backing type PropertyXForm. Such properties allow editors to select an existing form. The property it self will hold a reference to the selected form. In the dialog for selecting a form editors can also choose to create new forms.
Categories
EPiServer CMS features a basic category system. Categories can be configured in admin mode and one or more categories can then be selected as values in properties of type PropertyCategory. Such properties can be created in content type classes by adding C# properties of typeCategoryList.
The UI for creating, updating and deleting categories in admin mode.
The categories UI in admin mode after adding a few categories.
Populating a category property in forms editing mode.
When rendered using the PropertyFor method or the EPiServer:Property control a CategoryList is displayed as a comma separated list containing the selected categories names.
Page types
C# properties of type PageType map top the backing type PropertyPageType. Such properties store references to a single page type and are edited using a drop down list in which the page types on the site is listed.
If rendered using the PropertyFor method or the EPiServer:Property control a PageType property is displayed in the form a string, the page types name.
More property types
In the previous section we reviewed a number of property types that ships with the CMS. All of these have in common that properties with a specific backing type can be created by creating a property of a specific type in a content type class. The CMS then automatically maps the code property’s type to the backing type.
The CMS also ships with a number of other property types. These follow a different pattern; while there exists a backing type for each there’s no type that can be used for properties in content type classes to create such properties. Instead one option is to create a property of the suitable type, the same as the value type of the backing type, and map it to the backing type by annotating it with a BackingType attribute. However, for some of them another option is to create a property of the suitable type and add a UIHint attribute to it with an UI hint that maps to the property types editor.
Let’s take a look at these property types, starting with one named Language, for which we’ll also look at the two different approaches for creating such a property.
Languages
The backing type PropertyLanguage defines a property type for storing a language. Using this property editors get to choose one of the active languages on the site. That is, the property’s editor is not a general language selector but a way to select between the languages that pages can be created in. As you can imagine this property type isn’t very often used during regular site development.
In order to create a language property one approach is to create a string property and annotate it with a BackingType attribute, like this:
//using EPiServer.SpecializedProperties;
[BackingType(typeof(PropertyLanguage))]
publicvirtual string NativeLanguage { get; set; }
This property will be edited using a drop down menu and if rendered using the PropertyFor method or EPiServer:Property control the ISO code for the selected language, such as “en” or “sv”, will be outputted.
Looking at the property in admin mode we can see that the property indeed has the backing type PropertyLanguage, as represented by the more friendly text “Language” in the UI.
The PropertyLanguage backing type doesn’t do very much. While a property with this backing type will have a language selector as editor the value is a regular string. While it was necessary to create a separate property type in earlier versions of the CMS to change the editor this isn’t needed in EPiServer 7. Therefore another way of accomplishing almost exactly the same result is to instead create the property like this:
[UIHint("Language")]
publicvirtual string NativeLanguage { get; set; }
The above property will have the same editor and the same type of value as a property that has its backing type set to PropertyLanguage. However, when inspecting it in admin mode it won’t have a special backing type. Instead it will have the default backing type for string properties,PropertyLongString.
Property selector
A highly specialized property type that isn’t very commonly used is the backing type PropertySelector. Such properties allows editors to select one or multiple properties, or rather the names of properties, found on the same content type. The value of such a property is a comma separated list of property names.
As with the language selection property type, property selector properties can be created both by annotating a string property with a BackingType attribute or using an UIHint attribute. The below code sample illustrates both approaches.
//using EPiServer.SpecializedProperties;
[BackingType(typeof(PropertySelector))]
publicvirtual string Options { get; set; }
[UIHint("PropertySelector")]
publicvirtual string Options2 { get; set; }
Select list
Remember from our discussion about XHTML string properties that property types and editors can be associated with settings? As stated then, most property types that ship with the CMS don’t have any specific settings. The property type PropertyCheckBoxList, referred to as “Select list (multiple selection)” is one of a select few that does.
A select list property is edited as a list of check boxes that editors can check or uncheck. Visually its editor looks exactly like the property selectors, but it has a different data source. The options are defined for each such property using property settings.
Working with property settings for a select list property in admin mode.
Editing the same property in forms editing mode.
The value of a select list property is a string containing a comma separated list of the selected values. When rendered using the PropertyFor method or the EPiServer:Property control the string value is outputted.
As the value of these properties are strings, select list properties is created in content type classes as string properties with their backing type set to PropertyCheckBoxList, like this:
//using EPiServer.SpecializedProperties;
[BackingType(typeof(PropertyCheckBoxList))]
publicvirtual string Tastes { get; set; }
While there is a UI hint associated with the editor used by select list properties, it’s not possible to add a UIHint instead of using the BackingType attribute in order to create select list properties. Although a [UIHint("CheckBoxList")] changes the editor for a string property to the correct editor it’s not possible to specify what options should be available in admin mode.
Dynamic list/AppSettings
Similar to the select list property type that we just looked at, the two property types PropertyAppSettings and PropertyAppSettingsMultiple allow editors to choose between a predefined list of values. However, instead of using property settings, these property types fetch the available options from the appSettings element in web.config. More specifically, they fetch their available options from an app setting with the same name as the property.
Such app settings are expected to provide name/value pairs with a semicolon to separate the name and value and a pipe character to separate pairs. In the example below two such app settings have been added to the appSettings element.
<appSettings>
<add key="Office" value="USA;us|Sweden;sv|Norway;no" />
<add key="Subsidiaries" value="Denmark;dk|Finland;fi|France;fr" />
</appSettings>
In order to create properties that will utilize these lists we need to create string properties with a BackingType attribute and ensure that they have the exact same name as one of the app settings. In the code sample below two such properties are created, one of each type.
//using EPiServer.SpecializedProperties;
[BackingType(typeof(PropertyAppSettings))]
publicvirtual string Office { get; set; }
[BackingType(typeof(PropertyAppSettingsMultiple))]
publicvirtual string Subsidiaries { get; set; }
Properties with backing type PropertyAppSettingsMultiple have an editor in the form of a list of check boxes, just like select list properties. Properties with backing type PropertyAppSettings work differently and only allow editors to select a single value from a drop down list.
As with the select list property type, properties with backing type PropertyAppSettingsMultiple will have the selected values separated by commas as values. PropertyAppSettings property will have the single selected value as value.
As both backing types don’t do much other than change the editor used to for the property it’s possible to omit the BackingType attribute when creating properties of these types. When doing so we need to add UI hints instead, either [UIHint("AppSettings")] or[UIHint("AppSettingsMultiple")].
In the first version on EPiServer 7 properties that used the editors discussed here caused the CMS’ edit mode to break. This bug has been fixed in later releases. |
Sort orders
The property types PropertySortOrder and PropertyFileSortOrder can be used to create properties that describe how content or files should be sorted. For both property types the value is an integer that can be mapped to an enum. For PropertySortOrder the corresponding enum isEPiServer.Filters.FilterSortOrder and for PropertyFileSortOrder the corresponding enum is EPiServer.Web.PropertyControls.FileSortOrder.
When edited both property types are edited using a drop down list whose values map to the values in the above mentioned enums.
A PropertySortOrder property in forms editing mode.
A PropertyFileSortOrder property in forms editing mode.
The below code sample illustrates how to create properties of these two types by specifying a backing type. Note that it’s also possible to use their editors for integer properties by adding [UIHint("SortOrder")] or [UIHint("FileSortOrder")]. However, the two enum types does not have default mappings to the property types meaning that, for instance, a FilterSortOrder property without a BackingType attribute in a content type class will cause a runtime exception. As the enum values are convenient to work with the best way of creating properties like this is using theBackingType attribute, as shown below.
//using EPiServer.Filters.FilterSortOrder;
//using EPiServer.SpecializedProperties;
//using EPiServer.Web.PropertyControls;
[BackingType(typeof(PropertySortOrder))]
publicvirtual FilterSortOrder Order { get; set; }
[BackingType(typeof(PropertyFileSortOrder))]
publicvirtual FileSortOrder FileOrder { get; set; }
TinyMCE editor style sheets
Remember the setting named “Content CSS Path” from our earlier discussion about XHTML string properties? This setting is one way of defining the path for one, or several, CSS files that will be loaded into the TinyMCE editor used to edit XHTML string properties. This is an important feature of the CMS that we developers should know about, and utilize, in order to help editors.
Before we dive deeper into this feature and play with it on the site that we’re building, let’s look at a simple example of what this feature does. Let’s say we create a CSS file with the contents below and configure it to be used by the TinyMCE editor.
p.text-info {
EditMenuTitle: Text Emphasis;
EditMenuName: Info;
}
p.text-warning {
EditMenuName: Warning;
}
Now, when we edit a XHTML string property we can see the headings entered in the CSS file above in the Styles drop down list in the TinyMCE editor.
Note that we didn’t add any style rules in the CSS file, only the special attributes EditMenuTitle and EditMenuName. However, Twitter Bootstrap that we’re using on the site does have styling for the two CSS classes. Elements with the “text-info” class will have a blue font color and those with the “text-warning” class will have orange font color.
When selecting something in the Styles drop down the CSS class that have the corresponding name in the editor CSS file will be applied to the currently selected element in the TinyMCE editor. Therefore, if we place the cursor somewhere in the second paragraph and select “Info” in the Styles drop down list we’ll see the following result:
There are a few things to note in the above screen shot. First of all the second paragraph is blue in the preview on the left. Second, at the very bottom of the TinyMCE editor it says “Path: p.text-info” which confirms that the DOM path for the currently selected element in the edited XHTML string is indeed a paragraph with the “text-info” CSS class added to it.
Finally, note that inside the TinyMCE editor the second paragraph hasn’t changed at all. That’s because the editor doesn’t use the style sheet used on the site and we haven’t added any styling to the CSS file that it does use. It would of course be possible for us to add the same, or some simplified but similar, styling to the editor style sheet. However, it’s hard to make the preview in the TinyMCE editor truly What-You-See-Is-What-You-Get and it’s often a better idea to omit all, or at least complex, styling there and instead let editors focus on the raw text.
In other words, while it’s possible to define styling rules that will be applied to HTML elements when rendered in the editor in these CSS files, that’s typically not what they are used for. Instead they are used to define names for CSS classes and/or HTML element types that we want editors to be able to use.
By utilizing this feature we can make it easy for editors to use certain element types or CSS classes without having to modify the underlying HTML themselves. This way editors can, for instance, change the font size or font color without adding in-line CSS styles in the TinyMCE editor. Besides making life easier for editors this makes it possible for us to enforce consistent styling throughout the site that we can easily modify by simply modifying the styling of CSS classes.
Configuring CSS files used by the editor
The CSS file, or files, used by the TinyMCE editor can be defined in a number of ways:
· Through web.config, or more specifically in the configuration file episerver.config. To do this add an attribute named uiEditorCssPaths to the siteSettings element.
· By creating a string property named UIEditorCssPaths in a content type. If such a property exists in the currently edited content it will override the configuration file setting.
· Using property settings for XHTML strings in admin mode.
These three ways combined offer great granularity, making it possible to use different CSS files for individual properties. However, on many sites a single CSS file configured in episerver.config or in a globally used property setting is often enough.
Attributes
Editor CSS files can contain three special attributes that will be parsed and used in TinyMCE editors:
· EditMenuName defines a name for a style.
· EditMenuTitle inserts a heading into the drop down above the style’s heading.
· ChangeElementType controls whether selecting this style should also change the element type of the selected element.
We’ve already seen examples of what the first two attributes are used for. The last, ChangeElementType is used to control whether selecting the style should change the type of the HTML element that it’s applied to, in addition to applying a CSS class. If a style targets a specific element type, such as “p.text-info” in the example, it won’t have any effect when applying it to elements of other types unless ChangeElementType is set to true.
Localization
The values assigned to EditMenuName and EditMenuTitle attributes can be localized using language files. The CMS will look for translations in an editorstyleoptions element. A name or title for styles is excepted to reside in a child element with the name of the name or title lowercased and with spaces replaced by underscores.
An example editorstyleoptions element may look like this:
<editorstyleoptions>
<info>Information</info>
<warning>Warning</warning>
<heading_2>Heading 2</heading_2>
</editorstyleoptions>
Using editor style sheets
Let’s put our newly gained knowledge about editor style sheets to good use on the site that we’re building. Begin by creating a CSS file named “editor.css” in the “Content” folder in Visual Studio. With the file added configure it to be used by the MainBody property on the standard page page type.
As we previously discussed there are several ways to configure the path, or paths, for editor style sheets. On our fairly simple site there isn’t any need to have different editor style sheets for different properties of page types, meaning that we can either configure the path in episerver.config or in a global property settings. Here’s how to accomplish the latter:
1. Navigate to admin mode.
2. Click the “Page Type” tab and select the standard page page type (“[Editorial] Standard page”).
3. In the list of properties on the page type click “MainBody” property to edit it.
4. Click the “Custom Settings” tab.
5. Click the “Manage global settings” link.
6. Click the “Add Setting” button.
7. In the pop-up for creating a new setting enter “Standard” as name and “~/Content/editor.css” as value for “Content CSS Path”. Click the save button to save the new setting an close the pop-up.
8. The newly created setting appears in a table. On the right side of the table there’s a link with the text “Set as default”. Click that.
9. Done! Our custom style sheet will now be used for all XHTML properties on the site.
Now it’s time to add some styles to the CSS file. Twitter Bootstrap comes with a number of CSS classes that can come in handy for editors when working with text and images in XHTML string properties. However, before we add them we should start with the most obvious styles to add; headings.
Currently editors can’t add headings (h1, h2 etc) in the MainBody property. We could address that by going back to the property settings and add the “Format” drop down list to the editor. Using that editors would be able to select between all of the six heading tags.
However, that might not be a good idea as we probably don’t want editors to use the h1 tag. Also, the full range of headings probably isn’t needed. Therefore we’d like to make it easy for editors to create h2 and h3 tags but no other headings. To accomplish that we modify editor.css and add named styles for h2 and h3 elements.
h2 {
EditMenuTitle: Headings;
EditMenuName: Heading 2;
}
h3 {
EditMenuName: Heading 3;
}
Note that in the above CSS we didn’t add any class names. These styles will only be used to change the element type of a selected element. In other words, using these editors can put the cursor on a paragraph and select for instance “Heading 2” from the Styles drop down list to convert the paragraph to a h2 element.
Bootstrap adds styling for small elements (the HTML tag small). When such elements are created inside headings or paragraphs the text in the element will be lighter and smaller than the rest of the text. Adding a style for the small tag is done the same way as with headings. However, as this style isn’t exclusively related to headings we don’t want it grouped under the same heading. Therefore we’ll add it at the top of the file:
small {
EditMenuName: Small;
}
h2 {
EditMenuTitle: Headings;
EditMenuName: Heading 2;
}
h3 {
EditMenuName: Heading 3;
}
With these three styles in place it’s now possible to create h2, h3 and small elements by using the Styles drop down menu in the TinyMCE editor. Below is an example, featuring a heading of each size. Part of the first heading has been wrapped by a small tag by selecting that part of the text and selecting the “Small” option in the Styles drop down.
As we saw previously Bootstrap features a number of CSS classes that can be used to convey different meanings for text using color. There are six of these classes in total but on our site we only want to enable editors to use three of them, “text-muted”, “text-info” and “text-warning”. Adding styles for these is straight forward. We follow the same pattern as before only this time we make the styles for CSS classes rather than tags. We also group them under a separate heading.
p.text-muted {
EditMenuTitle: Text Emphasis;
EditMenuName: Info;
}
p.text-info {
EditMenuName: Info;
}
p.text-warning {
EditMenuName: Warning;
}
Note that we don’t just add the CSS classes above but make them target p tags specifically. This way editors can only use these styles for paragraphs. It will still be possible to select these styles when the currently selected element in the editor is some other element type, such as a heading. However, doing so won’t have any effect as neither of the styles include ChangeElementType: true;. If they had, selecting the style would change the currently selected elements type to a paragraph.
We’ve now made it possible for editors to modify text elements in a number of ways. Let’s also enable them to modify how images are displayed. Bootstrap comes with a few CSS classes that can be used to style images. In our case we’ll settle for one of them, the class “thumbnail” which adds some padding and a thin border to images.
In order to add a selectable style for the thumbnail class we add the class to our style sheet under a new heading and make it applicable only for img elements.
img.thumbnail {
EditMenuTitle: Images;
EditMenuName: Thumbnail;
}
Finally, let’s make it possible for editors to float elements either left or right by applying Bootstraps classes “pull-left” and “pull-right”. These will come in particularly handy for images but can also be used for other elements. As we don’t limit these the use of these styles to images or text there’s no logical grouping for them. Therefore we put them at the top of the file, together with the Small style. After doing so our complete editor.css file should look like this:
small {
EditMenuName: Small;
}
.pull-left {
EditMenuName: Float left;
}
.pull-right {
EditMenuName: Float right;
}
h2 {
EditMenuTitle: Headings;
EditMenuName: Heading 2;
}
h3 {
EditMenuName: Heading 3;
}
p.text-muted {
EditMenuTitle: Text Emphasis;
EditMenuName: Muted;
}
p.text-info {
EditMenuName: Info;
}
p.text-warning {
EditMenuName: Warning;
}
img.thumbnail {
EditMenuTitle: Images;
EditMenuName: Thumbnail;
}
We’re done with our style sheet for now. However, we still need to create translations for it. While this may seem unnecessary considering that we will only add translations in English it’s a good practice to do it anyway. Doing so will make it easier to add translations for other languages later on. It may also be that we’re not best suited to decide what the names should be and having them in a resource file allows us to send them to non-developers for modification.
In order to localize the names and titles in the style sheet create a new XML file in the “lang” folder in Visual Studio. Name it “EditorStyles.xml”. The content of the file should look like this:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<editorstyleoptions>
<small>Small</small>
<float_left>Float left</float_left>
<float_right>Float right</float_right>
<headings>Headings</headings>
<heading_2>Heading 2</heading_2>
<heading_3>Heading 3</heading_3>
<text_emphasis>Text Emphasis</text_emphasis>
<muted>Muted</muted>
<info>Information</info>
<warning>Warning</warning>
<images>Images</images>
<thumbnail>Thumbnail</thumbnail>
</editorstyleoptions>
</language>
</languages>
We’re now completely done with the editor style sheet. Take a minute to think about the difference in what editors could to with content prior to this work and now. As you may already have guessed; creating and working with editor style sheets is important. Below is an example of our style sheet in action5.
Validation
Sometimes we want to enforce restraints for values assigned to certain properties. Or we may want to force editors to enter a value for a property. Luckily, there are a number of ways to validate properties, the simplest of which is by using attributes.
Validation, for whom? When adding validation to properties it’s important to think about why we add it. First of all, EPiServer customers have bought a CMS to enable themselves to work with their content in flexible ways. We should keep that in mind when we start to limit what editors can do. However, with that said it’s important to help editors work efficiently in the CMS. Forcing them to enter values for certain properties and validating the values that they do enter can be a great help at times. The validation should however not be used as a substitute for defensive code. Just because we have instructed the CMS to validate that a property has a value doesn’t mean that we can count on such properties having values when working with them in code, such as when building templates. The validation may have been added after some content without values for the properties have been created and the validation doesn’t fix that. The validation may also later be removed as requirements from editors change. So, to summarize: use validation to help editors work efficiently but do not overuse it and don’t rely on it when writing code. |
Validation attributes
The .NET framework contains an abstract class named ValidationAttribute in the name space System.ComponentModel.DataAnnotations. It also contains a number of classes that inherits from ValidationAttribute, such as RequiredAttribute, RangeAttribute and StringLengthAttribute.
When an attribute that inherits from ValidationAttribute is used to annotate a property in a content type class EPiServer will call upon it when an instance of that class is being saved. For example, adding a RequiredAttribute to a property will cause the CMS to require that the property has been given a value. If it hasn’t the CMS won’t save the page and the editor will see an error message.
Here’s how the code would look with a RequiredAttribute added to the MainIntro property in the StandardPage class:
[Display(Order = 10)]
[UIHint(UIHint.Textarea)]
[Required]
publicvirtual string MainIntro { get; set; }
After adding the attribute, the CMS will validate that the property isn’t empty when working with standard pages.
Validation error in on page editing mode.
Validation error in forms editing mode.
Making a property required is also possible to do through EPiServer’s admin mode. As with most other settings for content types and properties, the RequiredAttribute can be thought of as providing a default value for this setting.
In addition to making properties required we can validate them in a number of other ways, either by creating our own attributes in the form of classes inheriting from ValidationAttribute, or by using some of the attributes that ship with the .NET framework. Some of the more useful of these include:
· EmailAddress - Requires a string property’s value to be a valid e-mail address. Requires .NET 4.5.
· StringLength - Used to enforce a maximum length on string properties. Does not work for XHtmlString properties.
· RegularExpression - Can be used to require that a string property’s value matches a regular expression.
· Range - Requires a numeric property’s value to be within a specific range.
IValidate
For our current page types and properties there isn’t really any need to add validation to any single property. However, it would be useful for visibility in search engines if all standard pages had a meta description. As we’ve implemented the MetaDescription property to fall back to the value of the MainIntro property when empty that means that we’d like to validate that editors have entered a value for at least one of the two properties.
That’s not something that validation attributes are useful for. In such, and other more complex validation scenarios, the CMS provides a different approach. We can create a class that implements the generic interface IValidate<T> in the name space EPiServer.Validation. The interface is simple, featuring a single method:
publicinterface IValidate<T> : IValidate
{
IEnumerable<ValidationError> Validate(T instance);
}
When implemented the type parameter T should be a content type class. EPiServer will locate classes implementing the interface and invoke the Validate method when the type parameter matches the type of content that an editor works with. In the method we are passed the content item and are expected to return a collection of validation errors, represented by the ValidationError class. If the returned list isn’t empty the CMS will refuse to save the content and display an error message to the editor.
Let’s create a custom validation class that validates that editors have entered a value for at least one of the two properties MetaDescription and MainIntro. We can place the class anywhere we want in our project, including making an existing class implement IValidate. Personally I’m a fan of making content type classes implement IValidate in order to place the validation logic close to what it validates. However, we’ll take a more conventional approach and create a new class named MetaDescriptionValidator in the Business folder in our project.
namespaceFruitCorp.Web.Business
{
publicclassMetaDescriptionValidator
{
}
}
Next we make the class implement IValidate<StandardPage>. After adding the necessary using statements and automatically implementing the method in Visual Studio the class should look like this:
usingEPiServer.Validation;
usingFruitCorp.Web.Models.Pages;
usingSystem.Collections.Generic;
namespaceFruitCorp.Web.Business
{
publicclassMetaDescriptionValidator : IValidate<StandardPage>
{
public IEnumerable<ValidationError> Validate(StandardPage instance)
{
thrownew System.NotImplementedException();
}
}
}
Finally we implement the Validate method making it return an IEnumerable with a single ValidationError if the validation fails and an empty IEnumerable if the validation succeeds. This is straight forward and requires little code thanks to the yield keyword in C#.
usingEPiServer.Validation;
usingFruitCorp.Web.Models.Pages;
usingSystem.Collections.Generic;
namespaceFruitCorp.Web.Business
{
publicclassMetaDescriptionValidator : IValidate<StandardPage>
{
public IEnumerable<ValidationError> Validate(StandardPage page)
{
if (string.IsNullOrWhiteSpace(page.MetaDescription))
{
yieldreturnnew ValidationError()
{
ErrorMessage = "A value for either Meta description or Preamble is required\
."
};
}
}
}
}
The above code appears to be missing a check for whether the MainIntro property has a value. However, remember that we implemented the MetaDescription property to return the value of MainIntro if it didn’t have an own value.
Now, when trying to save a standard page without values for MetaDescription or MainIntro editors will receive a validation error.
Our validation class is done in terms of functionality. However, we’ve hard coded the error message into the class, while it really belongs in a language file. So far we’ve only localized values that the CMS resolves using specific XPath expressions, but now we’ll have to use EPiServer’s localization API directly. We’ll look at that in a second, but first, let’s move error message into the ContentTypesEN.xml language file.
Abbreviated version of ContentTypesEN.xml after adding the error message
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
...
<validation>
<metadescription>A value for either Meta description or Preamble is required.</me\
tadescription>
</validation>
</language>
</languages>
In order to fetch translations from language files, or other sources of translations, we need an instance of the LocalizationService class found in the name space EPiServer.Framework.Localization. One way of getting a hold of such an instance is to use the static Current property on the class it self.
Once we have a LocalizationService object we can use its GetString method to fetch localized text. The GetString method expects a XPath expression and returns a matching translation. After adding a using statement for the EPiServer.Framework.Localization name space and using theLocalizationService.GetString method instead of the hard coded error message the MetaDescriptionValidator class should look like this:
usingEPiServer.Framework.Localization;
usingEPiServer.Validation;
usingFruitCorp.Web.Models.Pages;
usingSystem.Collections.Generic;
namespaceFruitCorp.Web.Business
{
publicclassMetaDescriptionValidator : IValidate<StandardPage>
{
public IEnumerable<ValidationError> Validate(StandardPage page)
{
if (string.IsNullOrWhiteSpace(page.MetaDescription))
{
yieldreturnnew ValidationError()
{
ErrorMessage = LocalizationService.Current
.GetString("/validation/metadescription")
};
}
}
}
}
One thing to note about our approach to localize the error message is that the error message uses the translated property names, or rather their captions, meaning that if the caption for the MainIntro property would be changed from “Preamble” to something else the error message should be changed as well. We could fix that by replacing the property captions in the text with placeholders and replace them with the captions for each of the properties using the string.Format method, should we want to get fancy.
Refactoring
This chapter has provided a fairly extensive look at how to create EPiServer properties of different types and how to customize their associated behavior. That’s all well and good, but what about when we want to change the type of an existing property? Or, remove an existing property?
In most situations doing either of those two actions is an irreversible action. When a property is removed the data associated with it, the property values, are removed from the database. Therefore EPiServer features a number of safe guards that protects the data.
Removing properties
Let’s say that we add a property named AuthorName to the StandardPage class. After compiling and making a request to the site the new property will appear in forms editing mode on standard pages and can be found in the list of properties for standard pages in admin mode.
Now, we decide to remove the property. So, we remove it from code and compile. The effect will be that the property is gone. It’s no longer editable in edit mode and won’t exist in admin mode.
This happens because the CMS recognizes that we have removed the property from code and can’t find any page where the property has been given a value. However, if we had entered a value for the property prior to removing it from code it wouldn’t be removed. Instead it would still be editable in edit mode and shown in admin mode. The CMS still recognizes that we’ve removed it from code though. When looking at the property in admin mode a message saying that the property was added by code, but isn’t any longer defined in code, is shown.
When this happens the delete button in the Edit Property dialog in admin mode is enabled and clicking it removes the property, along with its associated values. So, in order to remove properties we need to remove them from code and, if they have values, also remove them from admin mode.
Changing types
When we change the type of a property in code the CMS does its best to accommodate us. For instance, if we change the AuthorName property from a string to a XhtmlString property all we have to do is compile and make a request to the site and the property’s type will be changed. However, if we change the property’s type to something that isn’t either stored in the database in the same way as the old type, or that isn’t convertible from the old type, the backing EPiServer property’s type wont be changed.
In such cases there is no message in admin mode and we risk running into a runtime exception when the code property is accessed. In order to force the CMS to change the type we have to edit the property in admin mode and change its type by selecting an appropriate property type in theType drop down list. When doing so we should be aware that any existing property values will be irreversibly removed.
As an example of this behavior, let’s say we have the AuthorName property of type string. The property has been given a value on a page. If we change the property’s type to ContentReference in code and compile we’ll find that the property is still a string in the CMS.
We navigate to the property in admin mode and change it’s type to “Content Item” and save the property. Now the code and the database have the same opinion about the property’s type. However, the string value for the property is lost.
Next, we give the property, which is now of type ContentReference, a value on a page. Then we proceed to change the property’s type back to string in code and compile. We’ll now see that the property is automatically changed to a string in the CMS as well. This happens because aContentReference property can be converted to a string.
Renaming
Another challenge when modifying existing properties is renaming them. With content types we saw that it was possible, and recommended, to map content type classes to their counterparts in the database using a GUID. When doing so the CMS doesn’t use the class name to identify content types in the database but instead relies on the GUID.
However, for properties no such mapping exists. The property’s name is the only way for the CMS to identify properties. This means that it’s problematic to change names for existing properties, at least when the properties have values.
If an existing property is renamed in code EPiServer will regard this as two things happening; that a property with the old name has been removed and that a property with the new name has been added. Given that the property doesn’t have a value this works well. However, if the property does have values the newly renamed code property won’t return those and a property with the old name, but without connection to code, will still exist.
In order to maintain existing values when renaming properties we can use a fragile procedure of first changing the property’s name in admin mode and then changing it in code. This way no new property will be created and the new name of the property in code will match the property in the database by name.
However, this is tiresome during development and risky for sites in production. Luckily, EPiServer provides a solution; migrations.
Migrations
As we’ve seen the process of removing or changing the types for existing properties ranges from simply modifying properties in code to also making changes in admin mode. The same goes for renaming properties. While having to work with properties both in code and admin mode can be somewhat tiresome the process works fairly well during initial development of a site. However, for a site in production this is cause for headache.
When it comes to changing types for properties or removing properties with associated values there isn’t any good alternatives. However, for renaming properties there is; we can create classes that inherit the abstract class MigrationStep.
A class that inherits MigrationStep must implement a single method named AddChanges. Inside the method we can use a number of helper methods provided by the base class in order to rename properties or content types. On the first request after compiling the code EPiServer will find the migration and invoke the AddChanges method. As this happens prior to checking if any new properties have been added it’s possible to use migration classes to fairly easily change names for properties.
An example migration that changes the name of a property, in this case the “AuthorName” property to just “Author”, looks like this:
usingEPiServer.DataAbstraction.Migration;
namespaceFruitCorp.Web.Business
{
publicclassAuthorPropertyMigration : MigrationStep
{
publicoverridevoid AddChanges()
{
ContentType("StandardPage")
.Property("Author")
.UsedToBeNamed("AuthorName");
}
}
}
Migrations is a commonly used concept for dealing with database changes. Most migration frameworks work with versioned migrations that can be used to update the database as well as rolling it back to a previous state.
EPiServer migrations are simpler though, they can only be used to make changes to the names of content types and properties and aren’t versioned. As such they aren’t intended to be kept after they have been applied to all environments. In other words, once a migration has been executed on development machines as well as any test and production servers in a project the class can, and probably should, be removed.
Summary
This chapter has provided a look at the property concept in EPiServer. While we haven’t discussed underlying mechanisms in any great detail we’ve seen, and discussed, what “EPiServer properties” are as well as how they are created using properties in C#.
We’ve seen that a property in a content type is an instance of the PropertyData class. The various classes that inherit from PropertyData are referred to as “backing types” and enable us to store different types of values in the database. They also determine how the value should be edited.
We’ve reviewed the different types of properties that we can use out-of-the-box with EPiServer as well as how to customize, and validate, individual properties in various ways. We’ve also seen that we can create custom getters and setters for C# properties in content type classes. This allows us to implement logic, such as fall back behavior, in content type classes instead of spreading such logic around in templates throughout or project.
While not related to properties in general, but instead related to a specific property type, XHTML strings, we’ve had a look at how to create style sheets used by the TinyMCE editor. This is a small but important feature of the CMS that we as developers can utilize to make life easier for users, as well as to enforce consistent styling throughout the site.
Finally, we’ve discussed what happens when we make changes to, or remove, properties in code. We saw that this can be problematic, especially for sites that are in production. However, by knowing what the CMS does when it encounters renamed, or missing, properties in code we can at least identify potential problems. And, if the change to a property is a name change we now know that we can handle that by creating migration classes.
1. Meaning applying the Extract Method refactoring. Here this means that we create a new method containing all of the code used to assign values to the path variable and replace that code in the existing method with a single line, assigning the return value of that method to the pathvariable.↩
2. Using an open source project called Page Type Builder it was however possible to create page types using classes in versions 5 and 6.↩
3. A Globally Unique IDentifier. A series of characters, typically a number of hexadecimal digits, generated by a computer that is guaranteed to be unique on that machine.↩
4. XPath is a query language for selecting nodes from an XML document. A XPath expression such as /pagetypes/pagetype[@name=”StandardPage”] means “find pagetype elements inside a pagetypes element where the pagetype element has an attribute named name that equasl StandardPage”.↩
5. Photo of pineapple by SFAJane from Flickr, http://www.flickr.com/photos/sfajane/5112022746/.↩