You are using DataAnnotations for validations. From what I understand, you are looking for a way to apply the DataAnnotation validation to each element of the list.
Whenever Html.EditorFor is called, it fetches the ModelMetadata of the model that has been passed to it and then fetches any ModelValidators associated with that model. It is the presence of these ModelValidators that result in the 'data-val-*' attributes in the HTML.
When Html.EditorFor is passed a list as a model (or any enumerable for that matter) it first fetches the ModelMetadata and the associated Validators for the property - in your case, it will fetch ModelMetadata associated with the 'MyField' property followed by the validators - 'RegularExpression' in this case. It next iterates through the list of strings and gets the ModelMetadata and Validators for each string. While ModelMetadata has been constructed for each string, there are no Validators that have been specified for these strings. This is the reason that the string is displayed but the validation attributes are not added to the HTML element.
The way I see it, what you are looking for can be achieved by adding the Validator specified on the 'MyField' property to all the list elements at runtime.
This can be done by
- Writing a shared editor template for all Collections
- Setting the current ModelMetadataProvider to DataAnnotationsModelMetadataProvider
- Overriding the 'GetValidators' methd of DataAnnotationsModelValidatorProvider'
The shared editor template for step1 is given below
@model System.Collections.Generic.IEnumerable<object>
@{
ViewBag.Title = "Collection";
var modelMetadata = this.ViewData.ModelMetadata;
var validators = modelMetadata.GetValidators(ViewContext).ToList();
ViewContext.HttpContext.Items["rootValidators"] = validators;
}
@foreach (var item in Model)
{
@Html.EditorFor(m => item)
}
You can see in the code above that we are getting all the validators that have been specified on the list. These validators will be added to the elements of the list later. They have been stored in the HttpContext.Items for use in our custom ModelValidatorProvider.
Step 2 - In Global.asax, put in the following code -
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new DAModelValidatorProvider());
ModelMetadataProviders.Current = new CachedDataAnnotationsModelMetadataProvider();
Step 3 - Write your own ModelValidatorProvider by overriding the GetValidators method as shown in the code below
public class DAModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var validators = base.GetValidators(metadata, context, attributes).ToList();
// get root validators of the collection. this was stored in the editor template - fetching it for use now.
// fetching the rootvalidators inside this method is a bad idea because we have to call GetValidators method on the
// containers ModelMetadata and it will result in a non-terminal recursion
var rootValidators = context.HttpContext.Items["rootValidators"] as IEnumerable<ModelValidator>;
if (rootValidators != null)
{
foreach (var rootValidator in rootValidators)
{
validators.Add(rootValidator);
}
}
return validators;
}
}
Performing the above 3 steps did work for me. However, I've used Html.EditorFor instead of Html.TextBoxFor. Using Html.EditorFor, the way I have has not given me proper id and name attributes - I reckon this to be a trivial issue in the scheme of things. I've created a solution for this and uploaded it on https://github.com/swazza85/Stackoverflow so you can give it a go and see if it fits your needs. What I've done here is not a complete solution by any means but hopefully it gets you going without having to change your models.
Cheers,
Swarup.
data-val-regex = "MyErrorMessage"
, and notdata-val-required = "MyErrorMessage"
, since you don't have a[Required]
annotation on your property. – Humdrum