SomeShinyObject
SomeShinyObject

Reputation: 7821

Avoiding Repetitive View Code in ASP.NET MVC

This is for an ASP.NET MVC 4 application in a Razor view. I am passing a model to a partial view and am trying to iterate through a given list of properties contained in the model to be displayed as a table.

Given something like List<string> propertyNames, in this table, I would like to output the DisplayNameFor and the value of the property in a structure like so:

<tr>
    <th>@Html.DisplayNameFor(Model.property)</th>
    <td>@Model.property</td>
</tr>

I'll have to do this a few times in the partial because different properties correspond to different div elements in the partial where different tables will be inserted so I've created a helper and this is where I get hung up. First, the only way I know how to do this is reflection, and I have read that reflection is expensive especially for just doing one property at a time. Also, using this method, I can't get @Html.DisplayNameFor to work correctly because, using reflection, I don't quite know the syntax:

@helper IterateDetailPropertyNames(List<string> propertyNames) { 
    foreach (var property in propertyNames)
    {
        <tr>
            <th>
                @Html.DisplayNameFor(m => m.GetType().GetProperty(property).GetValue(Model, null));
                @*Error: Templates can be used only with field access, property access...*@
            </th>
            <td>
                @Model.GetType().GetProperty(property).GetValue(Model, null)
            </td>
        </tr>
    }
}

How can I make this work and improve this?

Upvotes: 1

Views: 115

Answers (1)

user3559349
user3559349

Reputation:

A models ModelMetadata gives you the data you need to generate your table. It contains the model value in additional to properties such as the DisplayName, DisplayFormatString, NullDisplayText etc that are determined from annotations applied to your properties.

For more information of ModelMetadata, refer the documentation and ASP.NET MVC 2 Templates, Part 2: ModelMetadata.

To generate your table, you could write the following HtmlHelper extension method

public static class TableHelper
{

    public static MvcHtmlString DisplayTableFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
    {
        // Get the ModelMetadata for the model
        ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
        StringBuilder html = new StringBuilder();
        // Loop through the ModelMetadata of each property in the model
        foreach (ModelMetadata property in metaData.Properties)
        {
            var x = metaData.DisplayFormatString;
            string label = property.DisplayName;
            string value = null;
            if (property.Model == null)
            {
                value = metaData.NullDisplayText;
            }
            else if (metaData.DisplayFormatString != null)
            {
                value = string.Format(metaData.DisplayFormatString, property.Model);
            }
            else
            {
                value = property.Model.ToString();
            }
            TagBuilder labelCell = new TagBuilder("td");
            labelCell.InnerHtml = label;
            TagBuilder valueCell = new TagBuilder("td");
            valueCell.InnerHtml = value;
            StringBuilder innerHtml = new StringBuilder();
            innerHtml.Append(labelCell.ToString());
            innerHtml.Append(valueCell.ToString());
            TagBuilder row = new TagBuilder("tr");
            row.InnerHtml = innerHtml.ToString();
            html.Append(row.ToString());
        }
        TagBuilder table = new TagBuilder("table");
        table.InnerHtml = html.ToString();
        return new MvcHtmlString(table.ToString());
    }
}

and in the view

@using YourAssembly.TableHelper
....
@Html.DisplayTableFor(m => m)

You can also include the assembly in your web.config file so that the @using statement is not required in the view

As s side note, <table> elements are for tabular data and should not be used for layout. Instead consider using styled <div> elements

Upvotes: 3

Related Questions