ASP.NET MVC inline helper for fields

asp.net-mvc c# expression-trees html-helper razor

Question

I need a generic inline helper to show fields.

Here is what I have currently:

@helper DisplayField(Func<MyModel, string> field)
{
    if (string.IsNullOrWhiteSpace(field(Model)) == false)
    {
        <div class="row"> @field(Model) </div>     
    }
}

Usage:

@DisplayField(m => m.Name)
@DisplayField(m => m.PhoneNumber)

I have no idea how to show Label(DisplayAttribute) inside of a helper. Please help.

Accepted Answer

At the top of your page, add in a using for System.Linq.Expressions, eg.

@using System.Linq.Expressions

(or add this namespace in web.config).

Then to create the helper, it would look something like:

@helper DisplayField(Expression<Func<MyModel, string>> field)
{
    @Html.LabelFor(field)
    @Html.DisplayFor(field)
}

You can add extra logic in there to check for empty value etc. You may be better off creating an HtmlHelper though, that way you can use generic type arguments rather than "MyModel" and "string" as above. When I last tried it was not possible to add generic type arguments to an inline helper, eg. the following is not possible, unless this feature has since been added:

@helper DisplayField<TModel, TValue>(Expression<Func<TModel, TValue>> field)
{
    @Html.LabelFor(field)
    @Html.DisplayFor(field)
}

So, to get the generic method, you would use a custom HtmlHelper. To do this:

  1. Create a file like the following:

    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
    
    namespace MvcApplication1.HtmlHelpers
    {
        public static class HtmlHelpers
        {
            public static MvcHtmlString DisplayFieldFor<TModel, TValue>(
                this HtmlHelper<TModel> helper, 
                Expression<Func<TModel, TValue>> field)
            {
                var labelString = helper.LabelFor(field);
                var displayString = helper.DisplayFor(field);
    
                return MvcHtmlString.Create(
                    labelString.ToString() + 
                    displayString.ToString());
            }
        }
    }
    
  2. In your page, the usage of such a thing would be:

    @Html.DisplayFieldFor(m => m.Name)
    @Html.DisplayFieldFor(m => m.PhoneNumber)
    

etc. You may need to check your usings on your page or add the namespace for the HtmlHelper into web.config.


Popular Answer

@David_001 provided a very elegant MVC-solution for this; I leave my answer here as it describes the inner mechanism and might be helpful if you need to solve a problem that is not covered by the MVC out of the box helpers.

Change the parameter type from Func<MyModel, string> to Expression<Func<MyModel, string>>. This way, you receive a dynamic expression in the method that you can analyze instead of the static delegate you receive in the current form.
You can retrieve the value of the field like this:

var compExpr = field.Compile();
var value = compExpr.DynamicInvoke(Model);

You can access the member and its attributes that is returned by the dynamic expression like this:

var memberAccExpr = (System.Linq.Expressions.MemberAccessExpression)field.Body;
var attr = (DisplayAttribute)memberAccExpr.Member.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault();

I tested it roughly in a C# project (not MVC), so I can't say whether it works in Razor; but I'm sure you can also move your code to a normal class.
Please note that you loose some type safety with this approach as a caller can provide a variety of dynamic expressions to the method. So you should take this into account if you analyze the expression in order to find the attribute.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why