MVC framework gives us a lot of different techniques for validating model and properties. The simplest way of model validation is to apply property attributes. After applying property attributes the client side validation will be performed automatically using unobtrusive JavaScript. The error message are being displayed in the UI if we provide @Html.ValidationSummary(true) or property level @Html.ValidationMessageFor(m => m…….). The trick is to apply range validation to checkbox as it don’t have a value when is not checked.
public class Client
{
[Required]
public string ClientName { get; set; }
[DataType(DataType.Date)]
[Required(ErrorMessage = "Please enter a date of birth")]
public DateTime DateOfBirth { get; set; }
[Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the terms")]
public bool AreTermsAccepted { get; set; }
}
One of the easiest validation methods is to validate model explicitly in the action method. We are simply adding the errors to ModelState object to be displayed in the UI when needed.
We can also check if binder was able to assign value by using ModelState.IsValidField. This is perfect for dateTime types. Checking business rules is also very easy with this technique.
[HttpPost]
public ViewResult CreateClient(Client client)
{
if (string.IsNullOrEmpty(client.ClientName))
{
ModelState.AddModelError("ClientName", "Please enter client name");
}
if (!client.IsTermsAccepted)
{
ModelState.AddModelError("IsTermsAccepted", "You must accept the terms to create account");
}
if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("IsTermsAccepted") && client.ClientName == "Mike")
{
ModelState.AddModelError("", "Mike is already registered");
}
if (ModelState.IsValid)
{
repository.SaveClient(client);
return View("Registered", client);
}
else
{
return View();
}
}
Another way of model validation is overriding DefaultModelBinder validation methods. The logic is basically similar to what is described above but it’s gives us the way for separating the validation logic and turning it on/ off when necessary by registering in global app start function.
By overriding the SetProperty function we find each property by name. Next, we check the values before it will be bound to the model. In OnModelUpdated function we perform model validation as this is being called after all model properties has been bound.
public class ValidatingModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
// call the base implementation
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
// do property level validation
switch (propertyDescriptor.Name)
{
case "ClientName":
if (string.IsNullOrEmpty((string)value))
{
bindingContext.ModelState.AddModelError("ClientName", "Please enter your name");
}
break;
case "AreTermsAccepted":
if (!((bool)value))
{
bindingContext.ModelState.AddModelError("AreTermsAccepted", "You must accept the terms");
}
break;
}
}
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// call the base implementation
base.OnModelUpdated(controllerContext, bindingContext);
// get the model
var model = bindingContext.Model as Client;
// perform model level validation
if (model != null && bindingContext.ModelState.IsValidField("ClientName") && model.ClientName == "Mike")
{
bindingContext.ModelState.AddModelError("", "Mike is already registered");
}
}
}
We also need to register our validator in the application global file:
ModelBinders.Binders.Add(typeof(Client), new ValidatingModelBinder());
Next form of validation is applying custom attributes where you can apply your specific validation rules.
public class IsTrueAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return value is bool && (bool)value;
}
}
[IsTrueAttribute(ErrorMessage="You must accept the terms")]
public bool AreTermsAccepted { get; set; }
We can also derive from existing attributes and add additional logic we need.
public class FutureDateAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
return base.IsValid(value) &&
value is DateTime &&
((DateTime)value) > DateTime.Now;
}
}
We also can apply custom model validation attribute by inheriting from ValidationAttribute. Please not that model validation wont be performed if there are some property level validation errors.
public class ClientValidatorAttribute : ValidationAttribute
{
public ClientValidatorAttribute()
{
ErrorMessage = "Mike is already registered";
}
public override bool IsValid(object value)
{
var client = value as Client;
if (client == null || string.IsNullOrEmpty(client.ClientName))
{
//we don't have right model to validate
return true;
}
else
{
return !(client.ClientName == "Mike");
}
}
}
//in our model class
[ClientValidatorAttribute]
public class Client {
Another way of validation is to create self-validating model by inheriting from IValidatableObject interface and overloading Validate function. The validation will be called after all property values has been assigned. This approach has benefit of having all model validation rules in the class definition.
public class Client : IValidatableObject
{
public string ClientName { get; set; }
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
public bool AreTermsAccepted { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> errors = new List<ValidationResult>();
if (string.IsNullOrEmpty(ClientName))
{
errors.Add(new ValidationResult("Please enter client name"));
}
if (DateTime.Now > DateOfBirth)
{
errors.Add(new ValidationResult("Please enter a date in the future"));
}
if (errors.Count == 0 && ClientName == "Mike")
{
errors.Add(new ValidationResult("Mike is already registered"));
}
if (!AreTermsAccepted)
{
errors.Add(new ValidationResult("You must accept the terms"));
}
return errors;
}
}
At the end I will show you how to create custom validation provider. In this approach we inherit from ModelValidatorProvider to use GetValidators method. This will allow us to apply custom validators on the very high level.
We basically check ContainerType to apply property level validation of the specified type and ModelType to apply custom model validator.
public class CustomValidationProvider : ModelValidatorProvider
{
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
{
if (metadata.ContainerType == typeof(Client))
{
return new ModelValidator[] { new ClientPropertyValidator(metadata, context)};
}
else if (metadata.ModelType == typeof(Client))
{
return new ModelValidator[] { new ClientValidator(metadata, context)};
}
return Enumerable.Empty<ModelValidator>();
}
}
Property validation looks like shown below.
public class ClientPropertyValidator : ModelValidator
{
public ClientPropertyValidator(ModelMetadata metadata, ControllerContext context)
: base(metadata, context)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var client = container as Client;
if (client != null)
{
switch (Metadata.PropertyName)
{
case "ClientName":
if (string.IsNullOrEmpty(client.ClientName))
{
return new ModelValidationResult[] {
new ModelValidationResult {
MemberName = "ClientName",
Message = "Please enter client name"
}};
}
break;
case "AreTermsAccepted":
if (!client.AreTermsAccepted)
{
return new ModelValidationResult[] {
new ModelValidationResult {
MemberName = "AreTermsAccepted",
Message = "You must accept the terms"
}};
}
break;
}
}
return Enumerable.Empty<ModelValidationResult>();
}
}
Model validator is also very similar. Please note that container value will be null in the Validate function.
public class ClientValidator : ModelValidator
{
public ClientValidator(ModelMetadata metadata, ControllerContext context)
: base(metadata, context)
{
}
public override void Validate(Client container, IList<ModelValidationResult> errors)
{
var client = (Client)Metadata.Model;
if (client.ClientName == "Mike")
{
errors.Add(new ModelValidationResult
{
MemberName = "",
Message = "Mike is already registered"
});
}
}
}
We register our provider in application global file:
ModelValidatorProviders.Providers.Add(new CustomValidationProvider());
As you can see the MVC framework allows us to apply a lot of validation methods that we can use. It should satisfy even the most complicated scenarios. Of course there is also client side validation that I skipped as this can be a topic for the separate article.