EPiServer  /  CMS April 20, 2013

How EPiServer's HTML helper PropertyFor works

PropertyFor is the counterpart of the Property control when building websites using EPiServer 7 and ASP.NET MVC. However, it does not work the same way as the Property control and understanding how it works is key to rendering the markup that you want and need.

When using Web Forms the standard/default/recommended way to render an EPiServer property is to use the EPiServer:Property control. When using ASP.NET MVC its counterpart is the HTML helper PropertyFor.

That is, PropertyFor is the counterpart the Property control in MVC in terms of being the recommended way to render a property. It is however not the equivalent of the Property control in terms of functionality.

Wrapping elements

As you may know, the Property control always wraps the property value in a HTML element. The element differs depending on the property type and can be customized using the CustomTag attribute on the control. This isn't the case with PropertyFor.

To exemplify, let's look at the code below where we use the PropertyFor method to render a string property.

<div>
    @Html.PropertyFor(x => x.Heading)
</div>

Given the Heading property has the value "Banana", the above will output the following when the page is rendered in view mode.

<div>
    Banana
</div>

When the page is rendered in edit mode the output will instead be:

<div>
    <div class="epi-editContainer" data-epi-property-name="Heading" data-epi-use-mvc="True">Banana</div>
</div>

As you can see, the property's value has been wrapped in a div with a CSS class and a couple of data-attributes. 

Conclusion: PropertyFor never wraps the property's value in an element when rendering it in view mode. It always wraps the property's value in an element when rendering it in edit mode.

CustomTag and CssClass

As I've previously written about, it's possible to pass settings such as CustomTag and CssClass to PropertyFor. With the Property control the CustomTag setting controls the type of element that the property is wrapped in and the CssClass setting adds one or more CSS classes to the wrapping element.

Let's modify the previous example to see what PropertyFor does with them.

<div>
    @Html.PropertyFor(x => x.Heading, new { CustomTag = "h1", CssClass ="muted" })
</div>

In view mode this changes the output to:

<div>
    Banana
</div>

That's right, it doesn't change the output at all.

What about when rendered in edit mode?

<div>
    <h1 class="epi-editContainer" data-epi-property-name="Heading" data-epi-use-mvc="True" data-epi-property-rendersettings="{&quot;customTag&quot;:&quot;h1&quot;,&quot;cssClass&quot;:&quot;muted&quot;}" class="heading">Banana</h1>
</div>

Starting with CustomTag we can see that setting it to h1 changed the type of the wrapping element that PropertyFor added in edit mode.

Conclusion: CustomTag changes the type of element that wrapes the rendered property when rendered in edit mode.

As for CssClass that added a class attribute with the specified value to the element. However, there already was another class attribute on the element and our custom CSS class wasn't added to that. It was added as a separate class attribute, meaning that the browser will ignore it. Here's how FireBug sees the rendered markup:

So, it appears that while CustomTag changes the type of wrapping element that PropertyFor renderes the property in in edit mode CssClass doesn't, in practice, have any effect on the class attribute on the wrapping element.

However, there's another setting we can pass to PropertyFor, EditContainerClass. Let's see what happens if we use that instead of CssClass, like this:

<div>
    @Html.PropertyFor(x => x.Heading, new { CustomTag = "h1", EditContainerClass ="muted" })
</div>

That produces the following when rendered in edit mode:

<div>
    <h1 class="muted" data-epi-property-name="Heading" data-epi-use-mvc="True" data-epi-property-rendersettings="{&quot;customTag&quot;:&quot;h1&quot;,&quot;editContainerClass&quot;:&quot;muted&quot;}">Banana</h1>
</div>

Look at that. No second class attribute has been added and the first has changed from "epi-editContainer" to the value we specified in EditContainerClass.

Conclusion: CssClass does not change the CSS class for the wrapping element when rendered in edit mode. EditContainerClass does.

Us specifying CustomTag, CssClass and, later, EditContainerClass also brought another change to the rendered markup in edit mode - a data-epi-property-rendersettings attribute was added to the wrapping element. The attributes value looks to be the serialized version of the anonymous object we used to pass the settings to PropertyFor with.

How PropertyFor renders property values

So far we've looked at how and when PropertyFor wraps the output for a property. But, beyond rendering a wrapping element in edit mode, how does it actually render a property?

In Web Forms the Property control uses the ClassFactory to resolve a control to which it delegates the actual rendering of the property. This way different controls are used for different types of properties. For instance, a string is rendered as is (wrapped in a p tag) while a LinkCollection is rendered as an ul-li list.

The PropertyFor method works quite differently but in a way that achieves similar results - it delegates the actual rendering to ASP.NET MVC's DisplayFor method.

Conclusion: In view mode PropertyFor isn't much more than an alias for DisplayFor. In edit mode it adds a wrapping element to whatever is rendered by DisplayFor. The wrapping element contains attributes that the CMS uses to identify editable properties.

DisplayFor and display templates

The DisplayFor method uses a number of rules to determine what to render for a given object. These, in an abbreviated form based on the official documentation on MSDN, are:

  • If the property is typed as a primitive type (integer, string, and so on), the method renders a string that represents the property value.
  • If the property type is Boolean, the method renders an HTML input element for a check box.
  • If a property is annotated with a data type attribute, the attribute specifies the markup that is generated for the property. For example, if the property is marked with the EmailAddress attribute, the method generates markup that contains an HTML anchor that is configured with the mailto protocol.
  • If the object contains multiple properties, for each property the method generates a string that consists of markup for the property name and markup for the property value.

What the documentation doesn't say clearly though is that the rendering is based on templates which can be overridden. That can be done by placing a partial view whose name matches the type of object that is to be displayed or whose name matches either a data type name or UI hint.

EPiServer adds a number of such templates to our site using a virtual path provider. These are the ones that handle the acutal rendering of EPiServer specific types such as LinkCollection, ContentArea etc.

Should we want to we can customize the rendering of such types by creating our own display templates. We can also specify that a specific template should be used for a specific property, no matter its type, by adding a UI hint to it.

PropertyFor and content areas

So far we've concluded that PropertyFor acts as a wrapper for DisplayFor and wraps the output from DisplayFor in an element, but only when invoked in edit mode context. There's seemingly an exception to that rule.

Let's look at what happens when we render a property of type ContentArea. The code may look like this:

<div>
    @Html.PropertyFor(x => x.Contents)
</div>

Given that the content area is empty this results in the following output in edit mode:

<div>
    <div class="epi-editContainer" data-epi-property-name="Contents" data-epi-use-mvc="True"></div>
</div>

Nothing surprising there. The call to PropertyFor results in a wrapping element, the div. Of course, since we're in edit mode that's expected.

Now, let's look at the output in view mode:

<div>

</div>

Nothing surprising there either. The wrapping element is gone. As we've already concluded that it should be in view mode.

Let's see what happens when we add something to the content area. In this case I've added a page for which there is a very simple partial renderer which simply outputs "Partial renderer output".

Here's the output in edit mode:

<div>
    <div class="epi-editContainer" data-epi-property-name="Contents" data-epi-use-mvc="True">
        <div data-epi-block-id="4" data-epi-content-name="Home">
            Partial renderer output
        </div>
    </div>
</div>

Again, no surprises. The wrapping element is there. The single content in the area is also wrapped in a div which is used to communicate information about item in the area to the CMS.

What about view mode then?

<div>
    <div>
        <div>
            Partial renderer output
        </div>
    </div>
</div>

That's quite a few div tags! Compare them to the output we saw in edit mode and you find  that the wrapping element for the property (the second div) is there, even though we're in view mode. 

It appears as though there's something treating content areas in a special way in PropertyFor, always outputting the wrapping element if the area isn't empty no matter in what context the area is being rendered.

That's not the case though. Instead it's the default display template for content areas that works in a way that gives that effect.

That display template delegates the rendering of the area to a method named RenderContentArea located in the class EPiServer.Web.Mvc.Html.ContentAreaExtensions. That method checks if the context is view mode and if it is outputs the wrapping element.

Why? Well, imagine you want to render the content area as an ul-li list. In that case you'd want to invoke PropertyFor with ChildrenCustomTagName set to "li" in order to make each item in the area wrapped with an li instead of a div.

Then you'd want to have the li tags wrapped in an ul. If you were to wrap the call to PropertyFor in an ul that would work great in view mode but you'd end up with <ul><div><li> in edit mode as PropertyFor always wraps the output in a div, or some other tag specified using CustomTag. 

On the other hand, if you didn't wrap the call to PropertyFor in an ul tag and set CustomTag to "ul" you'd end up with an ul-li list in edit mode but a lone set of li tags in view mode. To prevent that, the default display template for content areas, by means of the RenderContentArea method, outputs the wrapping element in view mode.

Conclusion: The default display template for content areas is implemented in such a way that the wrapping element is always outputted no matter if the property is rendered in view mode or edit mode, but only if the content area isn't empty.

Rendering properties without PropertyFor

While PropertyFor is the primary intended way to render properties when building EPiServer templates using MVC there's nothing that says that we have to use it. We're free to render properties of content objects such as PageData or BlockData, or properties of view models, however we want.

Of course, when doing so it's up to us to add the attributes the CMS needs to identify editable properties in preview/on-page-edit mode. To do that we can use the HTML helper extension EditAttributes. Using that method we feed it an expression which specifies a given property on the model in the view and it spits out whatever attributes the CMS needs on its wrapping element.

Here's a simple example:

<h1 @Html.EditAttributes(x => x.Heading)>@Model.Heading</h1>

One important thing to keep in mind though - when editing the page in on-page-edit mode the CMS will replace the value in the element that wraps the property value with the updated value. This means that if we're rendering something other than what the display template for the property does editors won't get a realistic preview.

How that works and how to handle that is an article of its own :)

Summary

  •  PropertyFor acts as a wrapper for DisplayFor. Therefor, to understand how PropertyFor works we need to look at how DisplayFor works.
  •  Beyond wrapping DisplayFor, PropertyFor wraps the output in an element needed by the CMS when in edit mode.
  •  The wrapping element is by default a div but can be changed using CustomTag.
  •  A custom CSS class can be added to the wrapping element when in edit mode using EditContainerClass.
  •  When used with content areas the wrapping element will be outputted in view mode as well due to how the default display template for ContentArea works.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

My book

Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!

More about EPiServer CMS