Posted in: Comments

Coming from WebForms development I was a bit annoyed with the inconsistent behavior of the PropertyFor() Html Helper. In WebForms properties are always wrapped with an element, but in MVC this is only true in edit mode. Please see Joel’s excelent post about PropertyFor() and what gets actually rendered to the client.

Conclusion: Properties always gets wrapped with an element in edit mode, but never in view mode. However this is only true for non ContentArea properties, which are not really the problem here.

Why is this a problem you might ask? Well this might actually break your design, especially in edit mode where the extra wrapping element might add styling that you don’t want. So how can we fix this?

One solution is to not use PropertyFor() at all and instead use DisplayFor() or just @Model.YourProperty. But then you need to add the editing attributes manually to a parent element but then that parent will always get rendered in view mode, even though the property doesn’t have a value. You can fix that by adding an if-statement around the parent and the property and then check whether we are in edit mode and/or the property has a value (we always need to render the parent in edit mode). Not so nice, and a lot of unnecessary code and logic in the view:

@if (EPiServer.Editor.PageEditing.PageIsInEditMode || Model.CurrentPage.PageHeading != null)
{
	<h2 @Html.EditAttributes(x => x.CurrentPage.PageHeading)>
		@Html.DisplayFor(x => x.CurrentPage.PageHeading)
	</h2>
}

Replacing the PropertyRenderer was the easist way I found to fix this behavior. Please let me know in the comment section if you have a better way of solving this! It was actually really easy:

public class SitePropertyRenderer : PropertyRenderer
{
    protected override MvcHtmlString GetHtmlForDefaultMode<TModel, TValue>(string propertyName, string templateName, string elementName, string elementCssClass, Func<string, MvcHtmlString> displayForAction)
    {
        if (string.IsNullOrEmpty(elementName))
        {
            return displayForAction(templateName);
        }

        var html = displayForAction(templateName).ToHtmlString();

        if (string.IsNullOrEmpty(html))
        {
            return MvcHtmlString.Empty;
        }

        var tag = new TagBuilder(elementName)
        {
            InnerHtml = html
        };

        if (string.IsNullOrEmpty(elementCssClass) == false)
        {
            tag.AddCssClass(elementCssClass);
        }

        return new MvcHtmlString(tag.ToString());
    }
}

We only have to override  how the property is rendered in view mode and always add the wrapping element. We also need to replace the default renderer in the container with our own, which can be done in an initializable module:

[InitializableModule]
public class ContainerConfigurableModule : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Container.Configure(container =>
            container.For<PropertyRenderer>().Use<SitePropertyRenderer>());
    }
}

Now we can write like this in the view instead, and the wrapper gets always rendered, but only in view mode if the property has a value:

@Html.PropertyFor(x => x.CurrentPage.PageHeading, new { CustomTag = "h2" })

This will probably not be an issue in MVC 6, hopefully in a near future, where we can use Tag Helpers instead.

I have some more blog post in the pipeline on the same topic; keeping the views and output clean. So stay tuned!