3

When using any of the Input Extension Helper Methods, like @Html.TextboxFor, any Validation Attributes from your model are automatically generated by the Razor engine (via ClientValidationEnabled/UnobtrusiveJavaScriptEnabled).

For example, take the following case which works fine

Model:

[Required]
public string QuestionOne { get; set; }

View:

@Html.TextBoxFor(model => model.QuestionOne) 
@Html.ValidationMessageFor(model => model.QuestionOne)

Generated Markup:

<input type="text" id="QuestionOne" name="QuestionOne" value=""
       data-val="true" data-val-required="The QuestionOne field is required." > 
<span class="field-validation-valid" data-valmsg-for="QuestionOne" data-valmsg-replace="true"></span>

In this case the attributes data-val="true" & data-val-required="The QuestionOne field is required." are picked up by Unobtrusive validation and the form element is successfully validated.


However, for extensibility reasons, I want to be able to generate the <input> element myself instead of using TextBoxFor. So my view would now look like this:

<input type="textbox" 
       id="@Html.IdFor(m => m.QuestionTwo)"
       name="@Html.NameFor(m => m.QuestionTwo)" 
       value="@Model.QuestionTwo"
       data-val="true" data-val-required="Selection is Required" />

@Html.ValidationMessageFor(model => model.QuestionTwo)

In this case, I'm faking the validation attribute output by just re-writing data-val="true" (etc) by hand, but this would have to be expanded to cover every single case.

Here's a running Demo in .NET Fiddle

Q: Can I build /return a list of data-val-* attributes for a given element?

KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 1
    The `HtmlHelper` has a `GetUnobtrusiveValidationAttributes()` method that returns the `data-*` attributes (but you need to get the `ModelMetaData` from the expression). All you have shown is the exact same html that will be generated by the `TextBoxFor()` method except for the `value` attribute if the property is invalid (because your no longer binding correctly). What are you wanting to achieve with this? –  Mar 24 '17 at 12:12
  • My uses are a little more varied. I'd like to find a way to add validation to [this custom ``](http://benfoster.io/blog/checkbox-lists-in-aspnet-mvc) element. – KyleMit Mar 24 '17 at 12:18
  • I know the first is yours, but that is an ugly solution.The way to handle creating custom html in cases like that is to create you own extension method where you have access to the `ModelMetadata` and `HtmlHelper` methods, and have complete control over your html (and in that example, the view code would be (say) `@Html.GeoDropdownListFor(m=> m.CityId, Model.Cities);` –  Mar 24 '17 at 12:26
  • @Stephen I'm having similar stumbles as OP. My trouble is that I don't know how to correctly implement my own HTML helper. All I have to work with is the source code, which is an absolute nightmare saturated in so many callbacks I have the hardest time finding the actual logic. – Sinjai Jul 19 '17 at 19:56
  • Did you not try the `GetUnobtrusiveValidationAttributes()` method? Or are you not understanding how to use it? –  Dec 05 '17 at 21:31
  • @StephenMuecke, thanks again, I missed that originally. It looks like it'll do exactly what I need. I'm fiddling around with it as per [How to use Html.GetUnobtrusiveValidationAttributes()](https://stackoverflow.com/q/8239929/1366033), but having trouble with the implementation in my case, but that's likely a new question by the time I can document how/why it's returning empty – KyleMit Dec 05 '17 at 21:46
  • I'll update the fiddle for you in an hour or 2 to show you how to implement it. But its still really unclear why you would want to do this –  Dec 05 '17 at 21:51
  • @StephenMuecke, Something wonky is happening with `GetUnobtrusiveValidationAttributes`, in that the first time I call the method I'll get real results, but [immediately refreshing it turns up nothing](https://i.imgur.com/OPSrYxn.png), which I suspect is creating a problem when calling it from inside an EditorTemplate. I'll write up separately what in the world I'm trying to do; just been trying to keep questions on topic and narrow in scope. – KyleMit Dec 05 '17 at 21:58
  • @StephenMuecke, The refresh conundrum is in the docs [`On the first call, a collection of attributes is returned. Further calls (on the same field) return an empty collection`](https://msdn.microsoft.com/en-us/library/system.web.mvc.htmlhelper.getunobtrusivevalidationattributes.aspx) - I wonder if that's what's clearing the collection when I try to use `Html.GetUnobtrusiveValidationAttributes(Html.NameFor(model => model).ToString())` inside of an `EditorFor()` if during the route there, it has already called that method behind the scenes – KyleMit Dec 05 '17 at 22:12
  • No, you misunderstanding that comment - if you have 2 inputs for the same property name, the `data-val-*` attributes will not be added for the 2nd one (I'll find a link for you shortly to explain). Just refreshing the page will not result in the attributes 'disappearing'. If they are its something else in your code causing the issue –  Dec 05 '17 at 22:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160549/discussion-between-stephen-muecke-and-kylemit). –  Dec 05 '17 at 22:18

1 Answers1

3

You can use the GetUnobtrusiveValidationAttributes() method of HtmlHelper to get the validation attributes associated with a specific property.

For example in the view

@{ var attributes = Html.GetUnobtrusiveValidationAttributes("QuestionTwo"); }

<input 
    type="textbox"
    @foreach(var attr in attributes)
    {
        @:@attr.Key="@attr.Value"
    }
    id="@Html.IdFor(m => m.QuestionTwo)"
    ....
/>

Note the @:@attr.Key="@attr.Value" line will give a warning (Missing attribute name) but will run correctly

Alternatively, you could use javaScript/jQuery to add the attributes

<script type="text/javascript">
    var attributes = @Html.Raw(Json.Encode(attributes));
    var input = $('#QuestionTwo');
    for(var i in attributes) {
        input.attr(i, attributes[i]);
    }
</script>

I have forked the DotNetFiddle here to show the working code for both options.

While the above code shows how it can be done, you should not be doing that. The HtmlHelper methods execute a lot of code your ignoring to ensure correct 2-way model binding, for example, the value attribute is determined by first checking for a value in ModelState, then in the ViewDataDictionary, and only if the previous values do not exist, does it use the value of the property (the second part of TextBoxFor displaying initial value, not the value updated from code explains the behavior).

Except for the incorrect value attribute, the code you have shown for the <input> is the same as will be generated by simply using @Html.TextBoxFor(m => m.Question2). I assume your real case is different, but if you cannot make use of TextBoxFor() and using an overload that accepts htmlAttributes to generate the html you need, then the correct approach is to create your own HtmlHelper method (and making use of existing methods in the HtmlHelper class and System.Web.Mvc.Html namespace)