1

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>
Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • 1
    In the template your model is `bool` so it just needs to be `@Html.LabelFor(m=> m)` etc. –  May 05 '15 at 00:09
  • Also not sure why you think creating a html helper extension method is a issue. [This answer](http://stackoverflow.com/questions/27303529/creating-a-helper-to-override-checkboxfor/27304443#27304443) gives an example of something fairly close to your output. –  May 05 '15 at 00:22
  • Ah ok, that makes sense - thank you. The trouble with the html helper is that there are too many classes in there that might potentially change. Hard coding those into the helper feels wrong to me. – Martin Hansen Lennox May 05 '15 at 12:13
  • All the helper is doing is what you template is doing, but means you can compile it into a separate dll and reuse it across multiple projects. Both approaches have merits, it just depends what you want to do and how you use the. But certainly if you want flexibility to be able to change attributes like class names, the template would be better. –  May 05 '15 at 12:19
  • I am not yet good enough to have my work compiled into dlls! – Martin Hansen Lennox May 05 '15 at 12:56

1 Answers1

1

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).

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • I think that's pretty cool too! Thank you for the detailed answer, and code samples, it's very helpful for me to see how it all fits together. – Martin Hansen Lennox May 05 '15 at 12:33
  • Are the commented out parts of object.cshtml where you've made changes from the default ? – Martin Hansen Lennox May 05 '15 at 12:35
  • Some yes some no. It's morphed through the generations ([started out as MVC2 object.ascx](http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html)). – Erik Philips May 05 '15 at 13:41