Reputation: 10784
Say I have a custom validation attribute ValidateFooIsCompatibleWith
model like so:
public class FooPart
{
public string Foo { get; set; }
public string Eey { get; set; }
}
public class FooableViewModel
{
public FooPart Foo1 { get; set; }
[ValidateFooIsCompatibleWith("Foo1")]
public FooPart Foo2 { get; set; }
}
Let's say I also have custom EditorTemplates defined for FooPart
:
@Html.TextBoxFor(m => m.Foo)
@Html.TextBoxFor(m => m.Eey)
And thus my view is essentially:
@Html.EditorFor(m => m.Foo1)
@Html.EditorFor(m => m.Foo2)
Server side, the validation works fine. However, no matter what I try, I can't get the rendered html to add the rule.
If I implement IClientValidatable
, it turns out that GetClientValidationRules()
never gets called. (I have successfully used IClientValidatable
with "simple" fields before).
I also tried registering my own custom adapter by inheriting from DataAnnotationsModelValidator<TAttribute>
and registering it in the global.asax
with DataAnnotationsModelValidatorProvider.RegisterAdapter(...)
That approach too fails to call GetClientValidationRules()
.
** Update **
If a add both a custom ModelMetadataProvider
and a custom ModelValidatorProvider
so that I can set breakpoints, I notice an interesting bit of behavior:
ModelMetadataProvider
for metadata with a ContainerType
of FooableViewModel
and a ModelType
of FooPart
. However, no corresponding request is made to the ModelValidatorProvider
, so I can't insert my custom client validation rules there.ModelValidatorProvider
with a ContainerType
of FooPart
and a ModelType
of string
for both the Foo
and Eey
properties. But at this level, I don't know the attributes applied to the FooPart
property.How can I get the MVC framework to register my custom client validation rules for complex types?
Upvotes: 1
Views: 828
Reputation: 10784
I found a solution:
First, Create a custom model metadata provider (see https://stackoverflow.com/a/20983571/24954) that checks the attributes on the complex type, and stores a client validatable rule factory in the AdditionalValues
collection, e.g. in the CreateMetadataProtoype
override of the CachedDataAnnotationsModelMetadataProvider
var ruleFactories = new List<Func<ModelMetadata, ControllerContext, IEnumerable<ModelClientValidationRules>>>();
...
var clientValidatable = (IClientValidatable)attribute;
ruleFactories.Add(clientValidatable.GetClientValidationRules);
...
result.AdditionalValues.Add("mycachekey", ruleFactories);
Next, register this as the default metadata provider in the global.asax
protected void Application_Start()
{
ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
....
}
Then I created an html helper that would process the modelmetata and create/merge the "data-val*" html attributes from each of AdditionalValues
collection.
public static IDictionary<string, Object> MergeHtmlAttributes<TModel>(this HtmlHelper<TModel>, object htmlAttributes = null)
{
var attributesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
//ensure data dictionary has unobtrusive validation enabled for the element
attributesDictionary.Add("data-val", "true");
//loop through all the rule factories, and execute each factory to get all the rules
var rules = ruleFactory(helper.Html.ViewData.ModelMetadata, helper.Html.ViewContext);
//loop through and execute all rules in the ruleFactory collection in the AdditionalValues
//and add the data-val attributes for those.
attributesDictionary.add("data-val-" + rule.ValidationType, ruleErrorMessage);
//similarly for anything in the rule.ValidationParameters
attributesDictionary.Add("data-val-" + rule.ValidationType + "-" + parameterName, parameterValue);
}
Finally, in my editor template, call the html helper (which has a model type of `FooPart1) for each complex type property, e.g.
@Html.TextBoxFor(m => m.Foo, Html.MergeHtmlAttributes(new { @class="Bar"}))
@Html.TextBoxFor(m => m.Eey, Html.MergeHtmlAttributes())
I actually ended up creating a second interface (with the same signature as IClientValidatable
) that allowed me to customize rules (primarily for error messages) for the individual fields of a complex type. I also extended the helper to take a string argument that could be formatted with my custom rules.
Upvotes: 1
Reputation: 865
jQuery.validator.setDefaults({
success: "valid"
});
$( "#foo" ).validate({
rules:
{
rule1: {required: true, min: 3},
parent:
{
required: function(element) {return $("#age").val() < 13;}
}
}
});
Complex types seem to hassle me for no good reason so try the Jquery validator. Depending on what you're trying to validate it might get the job done.
Upvotes: 0