Typed models in EPiServer 7
When looking at how developers using EPiServer 7 wrote their typed models we noticed a frequent use of the "BackingType" attribute. For developers used to EPiServer CMS 5 and 6 this might seem quite natural since everyone was forced to work with classes inheriting from PropertyData. Since one goal of EPiServer 7 has been to hide the internal property classes as much as possible and focus on the value types instead. We would really like to make it possible to define your models without the BackingType attribute for most of the use cases. If we take a look of what the property data classes have been responsible for it's mainly three things:
- Handling data.
- Defining a property control that has been responsible for:
- Rendering the property to the web site.
- Creating an editor for the property.
With the changes done to page-(content) editing in EPiServer 7 number 2.2 is no longer valid unless you use the legacy edit view.
In the latest CTP build you can give hint's on which editor should be used as described in my previous blog post:
After that we have done some more changes to be able to define your model without having to care about the backing property type. The first is that the most common types have their own value types, for instance XhtmlString, XForm and Url. If we look at how the model could look like it would be something like this:
1: using EPiServer.Core;
2: using EPiServer.DataAnnotations;
3: using EPiServer.XForms;
4:
5: namespace EPiServer.Templates.Alloy.Models.Pages
6: {
7: [ContentType]
8: public class TestPage : PageData
9: {
10: public virtual XhtmlString MainBody { get; set; }
11:
12: public virtual XForm Form { get; set; }
13:
14: public virtual Url ThankYouPage { get; set; }
15:
16: public virtual PageReference ListingRoot { get; set; }
17:
18: public virtual string Heading { get; set; }
19: }
20: }
And when editing in forms mode it will look like this:
But what if you would like to add an URL to an image? Creating a property with the type Url and defining an EditorHint("ImageUrl") attribute would make the Url be edited as an Image but the output on the page would still be a link to an URL instead of displaying an image. So in this case you would still have had to define a BackingType(typeof(PropertyUrl)) to select the correct Property Control.
To remove this need we have decided to use the UIHint attribute instead (from the System.ComponentModel.DataAnnotations namespace) and added support to select renderer depending on either this attribute or the Tag setting on a EPiServer Property web control, the same way as you can select a specific renderer for blocks. In this case the model would look like this:
1: using System.ComponentModel.DataAnnotations;
2: using EPiServer.Core;
3: using EPiServer.DataAnnotations;
4: using EPiServer.Web;
5:
6: namespace EPiServer.Templates.Alloy.Models.Pages
7: {
8: [ContentType]
9: public class TestPage2 : PageData
10: {
11: [UIHint(UIHint.Image)]
12: public virtual Url MyImage { get; set; }
13: }
14: }
Note: UIHint takes a string as a parameter but in this case we are using the UIHint class in EPiServer.Web that defines known hints as constants.
What happens behind the scenes in this case is that the property is backed by a PropertyUrl instance but a PropertyImageUrlControl will be used for rendering since it is declared as a renderer for the tag "Image" that matches the UI hint.
Using different hints for different layers
If you want to define a hint only selecting editor or renderer this is possible by defining the presentation layer property for the UIHint attribute. For instance:
1: using System.ComponentModel.DataAnnotations;
2: using EPiServer.Core;
3: using EPiServer.DataAnnotations;
4: using EPiServer.Framework.DataAnnotations;
5: using EPiServer.Web;
6:
7: namespace EPiServer.Templates.Alloy.Models.Pages
8: {
9: [ContentType]
10: public class MyPage : PageData
11: {
12: [UIHint(UIHint.Image, PresentationLayer.Edit)]
13: [UIHint("CustomImage")]
14: public virtual Url MyImage { get; set; }
15: }
16: }
Note: PresentationLayer also takes a string but there is a class with constants for known presentaion layers in EPiServer.Framework.DataAnnotations.
In this example EPiServer will use the default editor for selecting images but search for a registered template with the tag "CustomImage".
The BackingType attribute still works but should only be used where you have a custom property that actually does something with the data, not only assigns a custom property control. To make sure that your custom property controls can be assigned without having to have a custom property you will have to add a TemplateDescriptor attribute to the custom property control:
1: using EPiServer.Framework.DataAnnotations;
2:
3: namespace EPiServer.Web.PropertyControls
4: {
5: /// <summary>
6: /// Class for rendering image control.
7: /// </summary>
8: [TemplateDescriptor(TagString="CustomImage")]
9: public class CustomImageUrlControl : PropertyUrlControl, IRenderTemplate<Url>
10: {
11: //Class logic here
12: }
13: }
Default backing type for string
In current builds of EPiServer 7 we have used PropertyString as the backing type for the value type string. Since property string has a built in limitation of storing 255 characters you had to specify PropertyLongString as the backing type if you wanted to store strings longer than 255 characters. Since data storage of potentially long strings really is not an issue in the currently supported versions of MS SQL Server we have changed this to default to PropertyLongString which means that a string property has no length limitation by default. If you want to limit the length of strings you can use the StringLength attribute to validate the length.
We also changed the default editor for PropertyLongString to be a simple input instead of a textarea. If you want to edit the string using a textarea you can simply define a UIHint(UIHint.Textarea) to your string property.
Changes when working with weakly typed models.
The changes done to support the features above have some side effects when upgrading a site that has templates that works without typed models that you might want to be aware of:
Changed default editor for Property Long String
If you are upgrading a site with weakly typed page types and are using properties of type PropertyLongString you will get standard inputs instead of text areas. You can set the OperationCompatibility setting on your site settings element to include the flag LongStringLegacyControl to ensure that you still get text areas when editing your long string properties.
Changed type when accessing the Value property
Some of the changes to be able to implement this was to make sure that our property implementations returned an object of the type that said they returned. A properties return type is defined in the "PropertyValueType"-property. For some types, the internal storage type was returned instead of the defined type. For instance, PropertyXhtmlString, PropertyUrl and PropertyXForm all returned strings. Now these will return the value types XHtmlString, Url or Xform instead. For typed code you will probably not access data from the property collection directly in most cases (dynamic properties is a example where you still have to access data untyped) but for legacy code this might be a problem. For instance:
var value = (string)CurrentPage[“Mainbody”];
The code above would throw an exception in EPiServer 7 if the mainbody property was defined as an XhtmlString since the return value of the property is now an XhtmlString and not a string. So how do you deal with this? There are two ways:
1. Set the OperationCompabilityOption setting to include the flag LegacyPropertyValueType and these properties will still return strings in EPiServer 7. This can only be used if you are not using any typed models on your site.
2. Refactor the code where you are accessing the data untyped and casting it to a string. My colleague Magnus Rahl will create a separate post in this topic.
From an editor perspective, custom property types were often not used enough in EPiServer 5 and 6. That's probably because it was a bit cumbersome to develop them, especially if you wanted to base them on user controls instead of server controls.
This approach in CMS 7 eliminates the need for custom property types in many cases since they were often mainly used to customize the editor UI or template rendering, not the actual property value type. So this is a welcome addition in CMS 7! Thanks for a great write-up!