Reputation: 5917
Here's the situation, I have a list of about 20 properties (called Attributes) that I've defined in my database. This consists of a name, possible values, an optional regex, a boolean that indicates the field is required, etc.
In my ViewModel I get the list of attributes and in my view as List I have a nice EditorTemplate for AttributeViewModel to show them using Steve Sanderson's cool BeginCollectionItem to make sure the post gets bound back to a list of AttributeViewModel (this works just fine).
My AttributeViewModel looks like this:
public class AttributeViewModel
{
public string Description { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
public string SelectedValue { get; set; }
public byte RenderAs { get; set; }
public int AttributeID { get; set; }
public int ID { get; set; }
public int RegexValidation { get; set; }
public bool IsRequired { get; set; }
}
My View looks like this (edit.cshtml):
@model Company.Services.ViewModels.StaffMemberViewModel
<h2>Edit</h2>
@using (Html.BeginForm())
{
Some fields here, nothing of interest.
@Html.EditorFor(model => model.AttributeValues)
<input type="submit" value="Send" />
}
Here's the interesting bit though, this is my EditorTemplate for AttributeValues:
@using Company.Web.Helpers // This is where "BeginCollectionItem" lives
@model Company.Services.ViewModels.AttributeViewModel
using (Html.BeginCollectionItem("attributes"))
{
<div class="editor-label">
@Model.Description
</div>
<div class="editor-field">
@Html.DropDownListFor(m => m.SelectedValue, new SelectList(Model.Values, "Value", "Text"), "-- Select --")
@Html.HiddenFor(model => model.AttributeID)
</div>
}
What I would like to do is use the IsRequired and RegexValidation to make sure the SelectedValue for each attribute is valid. How would I go about doing so? If possible, I'd really like to take advantage of the MVC3 validation framework and unobtrusive validation like I "normally" would.
I obviously can't dynamically add a RequiredAttribute or a RegularExpressionAttribute as these differ for each of my attribute objects in the list.
Upvotes: 3
Views: 711
Reputation: 10924
This is untested. You may have to play with this to get your desired result.
First, create your custom DataAnnotationsModelValidatorProvider
class:
public class MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
{
internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory = Create;
internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories =
new Dictionary<Type, DataAnnotationsModelValidationFactory>()
{
{
typeof(RequiredAttribute),
(metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
},
{
typeof(RegularExpressionAttribute),
(metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
}
};
internal static ModelValidator Create(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
{
return new DataAnnotationsModelValidator(metadata, context, attribute);
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
List<ModelValidator> vals = base.GetValidators(metadata, context, attributes).ToList();
if (metadata.ModelType.Name == "SelectedValue")
{
// get our parent model
var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
metadata.ContainerType);
// get the associated AttributeId
var attributeId = Convert.ToInt32(parentMetaData.FirstOrDefault(p => p.PropertyName == "AttributeId").Model);
// get AttributeViewModel with specified AttributeId from repository
var attributeViewModel = _db.AttributeViewModels.FirstOrDefault(x => x.AttributeId == attributeId);
DataAnnotationsModelValidationFactory factory;
// check if required
if (attributeViewModel.IsRequired)
{
// must be marked as required
var required = new RequiredAttribute();
required.ErrorMessage = attributeViewModel.Description.Trim() +
" is Required";
if (!AttributeFactories.TryGetValue(required.GetType(), out factory))
factory = DefaultAttributeFactory;
vals.Add(factory(metadata, context, required));
}
// check for regex
if (attributeViewModel.RegexValidation > 0)
{
// get regex from repository
var regexValidation = _db.attributeViewModels.
FirstOrDefault(x => x.RegexValidation == attributeViewModel.RegexValidation);
var regex = new RegularExpressionAttribute(regexValidation.Pattern);
regex.ErrorMessage = attributeViewModel.Description.Trim() +
" is not in a valid format";
if (!AttributeFactories.TryGetValue(regex.GetType(), out factory))
factory = DefaultAttributeFactory;
vals.Add(factory(metadata, context, regex));
}
}
return vals.AsEnumerable();
}
}
Then, add the following to Application_Start
in Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MyModelMetadataValidatorProvider());
Upvotes: 1
Reputation: 761
I hope I am understanding your question correctly. You want to add custom validation attributes, annotation and validation logic to your views?
If so, you want to go to the System.ComponentModel.DataAnnotation
namespace. Your validation logic will be placed in a class deriving from ValidationAttribute
:
using System.ComponentModel.DataAnnotation;
public class MyValidationAttribute : ValidationAttribute
{
string readonly _validationParameter;
public MyValidationAttribute(string validationParameter)
{
_validationParameter = validationParameter;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
// add validation logic here
if (//not valid)
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
}
You can apply the attribute to any model property
[Required]
[MyValidationAttribute("parameter", ErrorMessage="Error in {0}")]
public string MyProperty { get; set; }
I hope this helps. See
page 127 for more info.
Upvotes: 0
Reputation: 41757
Consider using FluentValidation.Net (which is available via NuGet from the following Install-Package FluentValidation.MVC3
). It makes any sort of relatively complex data validation far simpler and more intuitive than a declarative style. There is support for client-side validation too.
Upvotes: 1