0

I have a couple of custom data annotations available to my editing ViewModels.

Because they can apply to any type of form control, I test for them in every EditorTemplate.

Can anybody recommend a way to reuse shared view code like this in MVC?

I'm considering a HtmlHelper or an AppCode/Helper class. Not sure which is best if either, I'm a bit of a newb with both.

@{
    var htmlAttributesFromView = ViewData["htmlAttributes"] ?? new { };
    var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesFromView, new { @class = "form-control" });


    bool isDisplayInfoOnIconClickAttribute = false;
    string title = "";
    string description = "";

    var infoOnClickAttributes = (ViewData.ModelMetadata).ContainerType.GetProperty(ViewData.ModelMetadata.PropertyName).GetCustomAttributes(typeof(DisplayInfoOnIconClickAttribute), false);
    if (infoOnClickAttributes.Length > 0)
    {
        DisplayInfoOnIconClickAttribute attribute = infoOnClickAttributes[0] as DisplayInfoOnIconClickAttribute;
        isDisplayInfoOnIconClickAttribute = true;
        title = attribute.Title;
        description = attribute.Description;
    }


    bool isDisplayTextInfoAttribute = false;
    string info = "";

    var textInfoAttributes = (ViewData.ModelMetadata).ContainerType.GetProperty(ViewData.ModelMetadata.PropertyName).GetCustomAttributes(typeof(DisplayTextInfoAttribute), false);
    if (textInfoAttributes.Length > 0)
    {
        DisplayTextInfoAttribute attribute = textInfoAttributes[0] as DisplayTextInfoAttribute;
        isDisplayTextInfoAttribute = true;
        info = attribute.Info;
    }
}



<div class="form-group">
    @Html.LabelFor(model => model, htmlAttributes: new { @class = "control-label col-md-3 text-right-md" })
    <div class="col-md-8">

        @Html.TextBoxFor(model => model, htmlAttributes)
        @Html.ValidationMessageFor(model => model)
    </div>
    <a class="infoonclick col-md-1" title="@Html.DisplayNameFor(model => model)" data-content="@Html.DescriptionFor(model => model)">
        <span class="fa fa-info-circle"></span>
    </a>
</div>
Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • That's an awful lot of code to be putting in an `EditorTemplate` and its a bit unclear what all the variables such as `isDisplayInfoOnIconClickAttribute`, `title`, `description` etc are for - you don't ever seem to use them. Typically, if you want to generate some html based on an attribute, then the attribute implements `IMetadataAware` which adds values to `ModelMetadata.AdditionalValues` and then you create your own `HtmlHelper` extension methods to output the html. –  Feb 23 '16 at 02:19
  • Ah cool, thanks. That sounds more like it. Do I need to put anything in `IMetadataAware.OnMetadataCreated()` or can I just leave that blank? Is there any chance you could provide a brief example of how I'd get the `ModelMetadata.AdditionalValues` into the `HtmlHelper`? That would def qualify as an anwser :) – Martin Hansen Lennox Feb 23 '16 at 02:35
  • Its a bit unclear exactly what you wanting to achieve, but [refer this answer](http://stackoverflow.com/questions/26519493/customattribute-reflects-html-attribute-mvc5/26519946#26519946) for an example of using an attribute that implements `IMetadataAware`. Whether you then create a `HtmlHelper` extension method as per the link, or whether you just read the `ViewData.ModelMetadata.AdditionalProperties` values in an `EditorTemplate` really depends on other factors. –  Feb 23 '16 at 02:47
  • Perfect, thanks a lot. I have added a revised code sample and hopefully it is clearer now. The code has now been simplified, and I think a `HtmlHelper` would be overkill. – Martin Hansen Lennox Feb 23 '16 at 17:25
  • Suggest you move your edit into a self answer and accept it so you can close this out. –  Feb 23 '16 at 21:46

1 Answers1

0

Thanks to Stephens suggestions I have put together a revised code proposition.

I have avoided using a HtmlHelper as it seems overkill for this requirement.

Model

public class Car : DbEntity, IDbEntity
{
    public virtual string Model { get; set; }

    [DisplayTextInfo("E.g. pink, yellow or green. For more colours please <a href='http://hslpicker.com/'>click here</a>.")]
    public virtual string Colour { get; set; }
}   

Data Annotation

public class DisplayTextInfoAttribute : Attribute, IMetadataAware
{
    public DisplayTextInfoAttribute(string info)
    {
        Info = info;
    }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        if (Info != null)
        {
            metadata.AdditionalValues["DisplayTextInfo"] = Info;
        }
    }
    public string Info { get; set; }
}

Editor Template

<div class="form-group">
    @Html.LabelFor(model => model)

    @Html.TextBoxFor(model => model, htmlAttributes)
    @Html.ValidationMessageFor(model => model)

    @if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DisplayTextInfo"))
    {
        <p class="help-block">@Html.Raw(ViewData.ModelMetadata.AdditionalValues["DisplayTextInfo"].ToString())</p>
    }
</div> 
Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • So many magic strings! Also if you used [MvcHtmlString](https://msdn.microsoft.com/en-us/library/system.web.mvc.mvchtmlstring(v=vs.118).aspx) instead of string for Info, then you wouldn't have to use Html.Raw(). – Erik Philips Feb 24 '16 at 00:52
  • Thanks Erik. I'll change that. How would you recommend I reduce the magic string count? As far as I'm aware you can't use variables in Data Annotation content. I guess I could move the `DisplayTextInfo` key into an AppSetting. What am I missing...? – Martin Hansen Lennox Feb 24 '16 at 01:29
  • Generally speaking, you have two different source code sources that use a special string that can be mistyped etc etc. If I have to use magic strings, I prefer to put a public const (in this case) on the `DisplayTextInfoAttribute` like `public const string Key = "DisplayTextInfo"` then reference `DisplayTextInfoAttribute.Key` instead of an actual string. – Erik Philips Feb 24 '16 at 08:03