Search code examples
asp.net-mvchtml-helpermvc-editor-templates

What is the preferred way to standardise complex html views for data types?


I have code like this that I repeat through many MVC editing views. This example is the default way we display a checkbox, but similar repetition is found with other input types.

<div class="form-group">
    @Html.LabelFor(model => model.IsLive, htmlAttributes: new { @class = "control-label col-md-3" })
    <div class="col-md-8 checkbox">
        <div class="col-xs-1">
            @Html.EditorFor(model => model.IsLive)
        </div>
        <div class="col-xs-10">
            @Html.CheckboxLabelFor(model => model.IsLive)
        </div>
    </div>
    <a class="infoonclick col-md-1" title="@Html.DisplayNameFor(model => model.IsLive)" data-content="@Html.DescriptionFor(model => model.IsLive)">
        <span class="fa fa-info-circle"></span>
    </a>
</div>

I am wondering what is the best way to DRY and standardise this?

I want to do something like @Html.DefaultCheckboxEditorFor(model => model.IsLive)

I tried creating a custom HtmlHelper, but this seemed to involve too many hard coded strings to be a good idea.

Rather I feel I should be using EditorTemplates for this, but I can't quite get the syntax right. The model for the view is a bool, but I need to get property specific stuff like the display name and descriptions.

 @model bool


<div class="form-group">
    @Html.LabelFor(model => model.IsLive, htmlAttributes: new { @class = "control-label col-md-3" })
    <div class="col-md-8 checkbox">
        <div class="col-xs-1">
            @Html.EditorFor(model => model.IsLive)
        </div>
        <div class="col-xs-10">
            @Html.CheckboxLabelFor(model => model.IsLive)
        </div>
    </div>
    <a class="infoonclick col-md-1" title="@Html.DisplayNameFor(model => model.IsLive)" data-content="@Html.DescriptionFor(model => model.IsLive)">
        <span class="fa fa-info-circle"></span>
    </a>
</div>

Solution

  • I have a project where most of my views look like: (This also works with multi-level deep complex objects, but not with any type of collection, like IEnumerable, although it could be modified to do so)

    <h3>Edit existing page</h3>
    
    <div class="col-xs-12">
        @using (Html.BeginForm("Edit", "Page", FormMethod.Post, new { role = "role" }))
        {
          @Html.EditorForModel()
          <input type="submit" value="Save" class="btn btn-primary" />
        }
    </div>
    

    I think that's pretty cool. So the model looks like:

    public class PageEditViewModel
    {
        [Editable(false)]
        [DisplayName("Page Id")]
        public Guid Id { get; set; }
    
        [Editable(false)]
        [DisplayName("Url to resource (format: '/my-resource' or '/sub/resource)'")]
        public string Url { get; set; }
    
        [Required]
        [MaxLength(50, ErrorMessage = "Maximum Length of 50 Exceeded.")]
        [DisplayName("Title for page (must match Url ex: 'My Resource' or 'Sub Resource'")]
        public string PageTitle { get; set; }
    
        [MaxLength(int.MaxValue, ErrorMessage = "Content Exceeded Maximum Length")]
        [DataType(DataType.MultilineText)]
        public string Content { get; set; }
    }
    

    I have some editor templates:

    \Views\Shared\EditorTemplates\multilinetext.cshtml

    @model object
    @{
      var htmlAttributes = this.ViewData.ModelMetadata.GetHtmlAttributes();
    }
    
    <div class="form-group @Html.ErrorClassFor(m => m, "has-error")">
        @Html.LabelFor(m => m, new { @class = "control-label" })
        <div class="controls">
            @Html.TextAreaFor(
                m => m,
                8, 8,
                htmlAttributes)
            @Html.ValidationMessageFor(m => m, null, new { @class = "help-block" })
        </div>
    </div>
    

    And it all magically works with the a modified version of object.cshtml:

    @model object
    
    @using System.Text;
    @using System.Data;
    
    @{
      ViewDataDictionary viewData = Html.ViewContext.ViewData;
      TemplateInfo templateInfo = viewData.TemplateInfo;
      ModelMetadata modelMetadata = viewData.ModelMetadata;
    
      System.Text.StringBuilder builder = new StringBuilder();
    
      string result;
    
      // DDB #224751
      if (templateInfo.TemplateDepth > 2)
      {
         result = modelMetadata.Model == null ? modelMetadata.NullDisplayText
                                                : modelMetadata.SimpleDisplayText;
      }
    
      foreach (var prop in modelMetadata.Properties.Where(pm =>
         pm.ShowForEdit
            //&& pm.ModelType != typeof(System.Data.EntityState)
         && !templateInfo.Visited(pm)
         )
         .OrderBy(pm => pm.Order))
      {
    
         //Type modelType =  Model.GetType();
         Type modelType = modelMetadata.ModelType;
         System.Reflection.PropertyInfo pi = modelType.GetProperty(prop.PropertyName);
         System.ComponentModel.DataAnnotations.DisplayAttribute attribute = pi.GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute), false).FirstOrDefault() as System.ComponentModel.DataAnnotations.DisplayAttribute;
    
         if (attribute != null
             && !string.IsNullOrWhiteSpace(attribute.GetGroupName()))
         {
            //builder.Append(string.Format("<div>{0}</div>", attribute.GetGroupName()));
            builder.Append(Html.Partial("Partial-GroupName", attribute.GetGroupName()));
         }
    
         builder.Append(Html.Editor(prop.PropertyName, prop.TemplateHint ?? prop.DataTypeName).ToHtmlString());
      }
    
      result = builder.ToString();
    }
    @Html.Raw(result)
    

    Example output:

    EditForModel

    My EditFor templates are versions of MacawNL BootstrapEditorTemplates (which I have no affiliation with).