Search code examples
asp.net-mvcmvc-editor-templates

EditorTemplates and ViewData


I have a ton of Forms that follow the common format of Label: Control. I decided instead of having pages of the following:

<div class="row-fluid">
     <div class="span4">
        @Html.LabelFor(m => m.Prop1)
    </div>
    <div class="span8">
       @Html.TextBoxFor(m => m.Prop1)
    </div>
</div>

I would create an editor template to hold this pattern, and select the correct default control. Of course, a teammate immediately pointed out that hardcoding the spans was not helpful, so I added a few custom ViewData fields such as "labelSpan".

The real problem comes to this:

 @Html.TextBoxFor(m => m, new { @class = @ViewData["class"], style = @ViewData["style"], 
id = @ViewData["id"]... etc })

So question 1: How do I consolidate that to pass through directly without having to specify every property?

Question 2: How do I do it in a way such that the following does not conflict?

@Html.LabelFor(m => m, new { @class = @ViewData["LabelClass"], style = @ViewData["LabelStyle"] })

Solution

  • Rather than using ViewData, you'll want to use ModelMetadata. If you create a custom ModelMetadataProvider, you can override the CreateMetadata method to add the appropriate values to the AdditionalAttributes and then you can later query those values.

        protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
                                                        Type containerType, Func<object> modelAccessor,
                                                        Type modelType, string propertyName)
        {
            var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
            var labelSpan = "span4"; // do whatever logic you need to.
            metadata.AdditionalValues.Add("labelSpan", labelSpan);
            ...
            return metadata;
        }
    

    Then you consume the metadata in your editor templates. The pattern that I've found to work is to only put the labels in your Object.cshtml editor template:

    @{
        var visibleProperties = ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)).ToList();
        var noHtmlProps = visibleProperties.Where(pm => pm.HideSurroundingHtml).ToList();
        var normalProperties = visibleProperties.Where(pm => !pm.HideSurroundingHtml).ToList();
        foreach (var prop in normalProperties)
        {
            <div class="row-fluid">
                @Html.Label(prop.PropertyName, prop.DisplayName, 
                    new{@class=ViewData.ModelMetadata.AdditionalValues["labelSpan"]})
    
                @(prop.IsReadOnly 
                    ? Html.Display(prop.PropertyName) 
                    : Html.Editor(prop.PropertyName))
                @Html.ValidationMessage(prop.PropertyName)
            </div>
        }
        // Output hidden properties at the end
        foreach (var prop in noHtmlProps)
        {
            @Html.Editor(prop.PropertyName)
        } 
    }
    

    Then you use your other types' editor templates to determine which control to use. For example, String.cshtml might look like this:

    @Html.TextBox(string.Empty, ViewData.TemplateInfo.FormattedModelValue,
        new{@class = ViewData.ModelMetadata.AdditionalValues["valueSpan"]})