0

I am attempting to make a custom HtmlHelper that will generate multiple HTML elements with the help of existing HtmlHelpers such as Html.TextBoxFor and Html.LabelFor. The primary issue I am running in to is that I cannot create Expressions to be used by these existing helpers unless they are all passed into my custom HtmlHelper individually. I would like to be able to pass the entire model to the custom HtmlHelper and create the Expressions inside of the helper and pass them to the existing ones. I am trying to do it this way in hopes that the attributes would still work, such as the Required and internationalization of the DisplayName. Is this possible?

I know I could do it by passing each of the Address properties into the custom HtmlHelper via expression, but it doesn't feel as clean and easy to use as having it done within the helper.

Address Model

public class Address
{
    [Required(ErrorMessage = "Required"), DisplayName("First Name")]
    public String FirstName { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("Last Name")]
    public String LastName { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("Line 1")]
    public String Line1 { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("Line 2")]
    public String Line2 { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("City")]
    public String City { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("State")]
    public String State { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("Country")]
    public String Country { get; set; }

    [Required(ErrorMessage = "Required"), DisplayName("Postal Code")]
    public String PostalCode { get; set; }
}

Custom HtmlHelper

public static class AddressExtension
{
    public static MvcHtmlString AddressEditorForModel<TModel>(this HtmlHelper<TModel> helper, Address address, String prefix, Boolean showLabels)
    {          
        // There will be more markup in here, this is just slimmed down..

        StringBuilder sb = new StringBuilder();
        sb.AppendLine(String.Format("<div id='{0}_AddressContainer' >", prefix));

        // First Name
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // Last Name
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // Line 1
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // Line 2
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // City
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // State
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // Country
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        // Postal Code
        if (showLabels)
            sb.AppendLine(helper.DisplayFor(????).ToString());            
        sb.AppendLine(helper.EditorFor(????).ToString());

        sb.AppendLine("</div>");

        return MvcHtmlString.Create(sb.ToString());
    }

View (cshtml)

@Html.AddressEditorForModel(Model, "test", true)

1 Answer 1

1

You can do this with reflection and expression trees like this:

public static MvcHtmlString AddressEditorForModel<TModel>(this HtmlHelper<TModel> helper, string prefix, bool showLabels)
{          
    // There will be more markup in here, this is just slimmed down..

    StringBuilder sb = new StringBuilder();
    sb.AppendLine(String.Format("<div id='{0}_AddressContainer' >", prefix));

         //Create a parameter expression for the model type
         ParameterExpression paramExpr = Expression.Parameter(typeof (TModel));
         //Loop through the properties you want to create a DisplayFor for
         foreach (var property in typeof (TModel).GetProperties())
         {
             //Create a member access expression for accessing this property
             MemberExpression propertyAccess = Expression.MakeMemberAccess(paramExpr, property);
             //Create the lambda expression (eg. "x => x.Name")
             var lambdaExpr = Expression.Lambda<Func<TModel, string>>(propertyAccess, paramExpr);
             //Pass this lambda to the DisplayFor method
             sb.AppendLine(helper.DisplayFor(lambdaExpr).ToString());

         }

...rest of your method

However this assumes that TModel is your Address object, so you also don't need to pass it in. The TModel in HTML helpers comes from a strongly typed view, so if you have a view where the @model type is Address then you can use the method as above

the view would use it like this:

@model Address

@Html.AddressEditorForModel("prefix", true)
Sign up to request clarification or add additional context in comments.

2 Comments

This works quite well, thank you! I wasn't quite sure about how the TModel worked, so thank you for that explanation. Is there a way to only allow it to be used with an Address that is passed in instead of the generic TModel?
You could put a generic type constraint "where TModel : address" on the method decleration. This would mean your method will only show up in intellisense for "Html." helper when the model was Address. See here for more information on generic type constraints: msdn.microsoft.com/en-gb/library/d5x73970.aspx

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.