Build list of data validation attributes for a given element
Asked Answered
O

1

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?

Onym answered 24/3, 2017 at 12:5 Comment(11)
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?Glooming
My uses are a little more varied. I'd like to find a way to add validation to this custom <select> element and to this <input typed="checked"> element.Onym
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);Glooming
@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.Tense
Did you not try the GetUnobtrusiveValidationAttributes() method? Or are you not understanding how to use it?Glooming
@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(), 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 emptyOnym
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 thisGlooming
@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, 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.Onym
@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 - 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 scenesOnym
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 issueGlooming
Let us continue this discussion in chat.Glooming
G
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)

Glooming answered 8/12, 2017 at 4:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.