Custom renderers for properties
In my previous blog post about updates to typed models in EPiServer 7 I mentioned that we have added the possibility to select renderer for properties, pretty much the same way as you can assign renderers for blocks and pages. Let’s look a bit more into how this works. We begin with defining a simple page type with a single property:
using System.ComponentModel.DataAnnotations;
using EPiServer.Core;
using EPiServer.DataAnnotations;
using EPiServer.Web;
namespace EPiServer.Templates.Alloy.Models.Pages
{
[ContentType(GUID = "F8D47655-7B50-4319-8646-3369BA9AF05E")]
public class MyPage : PageData
{
[UIHint("email")]
public virtual string Email { get; set; }
}
}
Let’s create a simple renderer for the property. We’ll start by creating a property that inherits from one of the built in Property Controls: PropertyStringControl.
using System.Web.UI.WebControls;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.PropertyControls;
using EPiServer.Web;
[TemplateDescriptor(TagString = "email")]
public class EmailPropertyControl : PropertyStringControl, IRenderTemplate<string>
{
public override void CreateDefaultControls()
{
var link = new HyperLink();
link.Text = PropertyData.Value.ToString();
link.NavigateUrl = "mailto:" + PropertyData.Value.ToString();
Controls.Add(link);
}
}
This class is very similar to what you would do in EPiServer CMS 5 and 6. There are two differences though. The first is that it implements the IRenderTemplate<T> interface. The second is the TemplateDescriptor attribute. This tells the system that this editor is preferred when rendering a model property with a UIHint attribute that matches the TemplateDescriptor Tag/TagString properties(Tag and TagString are basically the same property but with different types. Tags is defined as a string array which is not possible if you have an CLS-compliant project).
In our page template we add a standard EPiServer property web control to display the property:
<EPiServer:Property PropertyName="Email" runat="server" />
When viewing a page of this type we get a simple a tag with a mailto-link:
Defining tags in the template
In the first example we added a UIHint attribute to our model. But what if we want to display the model differently in different templates? This can be done by assigning a tag in the RenderSettings property of the EPiServer property web control. Let us assume that we are working for a fictitious client that imports bananas. Their web strategists have decided that they should have a lot of images of bananas on their web site and what better way to implement this than with the great dancing banana image. Lets add a tag to the property:
<EPiServer:Property PropertyName="Email" runat="server" />
<RenderSettings Tag="emailgoesbananas" />
</EPiServer:Property>
And we’ll create a new renderer that will add a dancing banana next to the mail link:
using System.Web.UI.WebControls;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.PropertyControls;
using EPiServer.Web;
[TemplateDescriptor(TagString = "emailgoesbananas")]
public class BananaEmailPropertyControl
: PropertyStringControl, IRenderTemplate<string>
{
public override void CreateDefaultControls()
{
var link = new HyperLink();
link.Text = PropertyData.Value.ToString();
link.NavigateUrl = "mailto:" + PropertyData.Value.ToString();
Controls.Add(link);
Controls.Add(new Image()
{ ImageUrl = "http://www.sherv.net/cm/emo/funny/2/banana.gif" });
}
}
And behold: We have a dancing banana!
Using a user control to render the property
Another new feature is the ability to define a user control to render your property. To do this you have to inherit from the generic class PropertyControlBase:
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web;
namespace EPiServer.Templates.Alloy.Views.Pages.Partials
{
[TemplateDescriptor(TagString = "bananasgoesbananas")]
public partial class EvenMoreBananas : PropertyControlBase<string>
{
}
}
Let’s say the customer was thrilled with the dancing banana and just want more. To speed up the development we’ll add these to the user control instead:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="EvenMoreBananas.ascx.cs"
Inherits="EPiServer.Templates.Alloy.Views.Pages.Partials.EvenMoreBananas" %>
<asp:Panel runat="server" ID="BananaWrapper">
<a href='mailto:<%=CurrentData %>'>
<% 1: =CurrentData
%></a>
<img src="http://www.sherv.net/cm/emo/funny/2/banana.gif" />
<img src="http://www.sherv.net/cm/emo/funny/2/upside-down-banana-smiley-emoticon.gif" />
<img src="http://www.sherv.net/cm/emo/funny/2/banana-with-bagpipes-smiley-emoticon.gif" />
<img src="http://www.sherv.net/cm/emo/funny/2/woohoo-dancing-banana-smiley-emoticon.gif" />
</asp:Panel>
Note: CurrentData in this case is our model value typed as a string as defined in our inheritance declaration.
We change the tag in our page template to the one defined in the user control, bananasgoesbananas, and reloading the page gives us:
When clicking on the email text we still get the default editing (inline editing for strings). This is because the user control is wrapped in a generic property control that handles editing attributes so that you don’t have to care about this.
Sum up
With these additions working with complex models, like pages and blocks, and simple properties become pretty much the same. You can:
- Register renderers for them using the TemplateDescriptor attribute.
- Select renderer using the UIHint attribute..
- …or by defining a tag in RenderSettings Property for the EPiServer property web control.
If no renderer is found given the specified tags PropertyControlClassFactory will fall back to the current behavior.
This looks very promising, it would be nice if it was possible to pass custom variables to the renderer using the RenderSettings (And UI hint perhaps). Let's say you would like to implement a "DateTime-renderer", with a customizable date-format, which would go something like this:
Or...
Etc..
You digg? :)
Björn: You can add any attribute to the RenderSettings property and you can access them in your property control like this:
var customVariable = RenderSettings["CustomVariable"];
This property is currently only accessible for if you create a class derived from PropertyData and not when creating a user control as a renderer.
Sweet, sounds perfect.
Hello, I am currently attempting something similar to Björn in that I would like to render a DateTime property using a specific format. Unfortunately, even when I create a control which inherits from PropertyDateControl and implements IRenderTemplate, it doesn't seem to do anything.
I've tagged the DateTime property via its RenderSettings, but I'm still not seeing any effect. Is there something I am doing wrong?
Here is my property code:
[TemplateDescriptor(Tags = new string[] { Constants.RenderingTagNames.DateTime })]
public class FormattedPropertyDateControl : PropertyDateControl, IRenderTemplate
{
public override void CreateDefaultControls()
{
Label lblDateTime = new Label();
lblDateTime.Text = Date.ToString(RenderSettings["DateFormat"].ToString());
Controls.Add(lblDateTime);
}
}
I tested your code and found out that the property data type in this case is defined as Nullable. Perhaps a bit confusing since I guess that the typed model uses DateTime but changing to IRenderTemplate> should make the control active.