Need to write your own model validator outside the scope of an application framework such as ASP.NET MVC? A short while ago, I needed to do just that. I was writing a WCF service with a relatively complex data model which required a much greater level of validation than the DataMember attribute’s IsRequired property could provide.
Here’s the solution I found. But first a bit of background.
Validation in ASP.NET MVC
I’ve been using Data Annotations attributes for view model validation for years in the context of ASP.NET MVC for model validations, client and server side. I always took the server side validation for granted and looked at the client side with greater interest. When getting started with ASP.NET MVC, I used Steve Sanderson’s xVal library. Then I switched to ASP.NET MVC 3’s client side validation.
For client side validation, I’m really starting to like jQuery’s unobtrusive validation and the ASP.NET MVC’s HtmlHelper class and its GetUnobtrusiveValidationAttributes method. And for the server side, view model binding and the ModelState.IsValid property works fine.
Validation Without an App Framework
But this post is not about client side validation or server side validation in ASP.NET MVC. I had to do the validation against the model in the WCF service which, as far as I know, does not have a nice model validation facility other than its own data model serialization which just throws a nasty exception should the data being passed over the wire not provide required data.
So why not use the same approach used by our friends in the ASP.NET MVC world. A little Google investigation turned up something that looked like exactly what I wanted. I found an answer posted to StackOverflow by Mike Reust which included a link to his DataAnnotationsValidatorRecursive library. (Gotta love StackOverflow.)
After some experimentation, I had made some tweaks to Mike’s code and came up with something that worked the way I wanted it to. Here’s a partial example of a data model and how the ValidateObject with it’s optional MinOccursOnEnumerable can be used to require one or more items to be included in a required complex enumerable type that will get recursively.
[DataMember(IsRequired = true),
Required(ErrorMessage = "Employer cannot be null."),
ValidateObject]
public Employer Employer { get; set; }
[DataMember(IsRequired = true),
Required(ErrorMessage = "Children cannot be null."),
ValidateObject(MinOccursOnEnumerable = 1)]
public Child[] Children { get; set; }
Using the recursive validation library is easy. Here’s an example:
//perform validation using DataAnnotations for custom validation messages
List<ValidationResult> results = new List<ValidationResult>();
bool isValid = DataAnnotationsValidator.TryValidateObjectRecursive<MyModelData>(model, results);
//now examine isValid and the results
And here’s the code for the validation extensions and ValidateObject attribute.
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System;
namespace MyValidator
{
public static class DataAnnotationsValidator
{
public static bool TryValidateObject(object obj, ICollection<ValidationResult> results)
{
return Validator.TryValidateObject(obj, new ValidationContext(obj, null, null), results, true);
}
public static bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results)
{
bool result = TryValidateObject(obj, results);
var properties = obj.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(ValidateObjectAttribute)));
foreach (var property in properties)
{
var valAttrib = property.GetCustomAttributes(typeof(ValidateObjectAttribute), true).FirstOrDefault() as ValidateObjectAttribute;
var value = obj.GetPropertyValue(property.Name);
if (value == null || valAttrib == null) continue;
var asEnumerable = value as IEnumerable;
if (asEnumerable != null)
{
List<object> items = new List<object>();
foreach (var enumObj in asEnumerable) items.Add(enumObj);
foreach (var enumObj in items)
{
result = TryValidateObjectRecursive(enumObj, results) && result;
}
if (items.Count < valAttrib.MinOccursOnEnumerable)
{
string errorMessage = valAttrib.ErrorMessage ?? "MinOccursOnEnumerable validation failed.";
results.Add(new ValidationResult(errorMessage));
result = false;
}
}
else
{
result = TryValidateObjectRecursive(value, results) && result;
}
}
return result;
}
}
public static class ObjectExtensions
{
public static object GetPropertyValue(this object o, string propertyName)
{
object objValue = string.Empty;
var propertyInfo = o.GetType().GetProperty(propertyName);
if (propertyInfo != null)
{
objValue = propertyInfo.GetValue(o, null);
}
return objValue;
}
}
}
You need to use the ValidateObject attribute on any complex type you want validated deeply. I found out the hard way that if you try to validate all reference objects, you get nasty results on DateTime properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyValidator
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public class ValidateObjectAttribute : Attribute
{
int _minOccurs = 0;
//marker for object properties that need to be recursively validated
public ValidateObjectAttribute() { }
public int MinOccursOnEnumerable { get { return _minOccurs; } set { _minOccurs = value; } }
public string ErrorMessage { get; set; }
}
}
Of course you can take off on this or Mike’s original code and create your own validation library for a WCF service, a business logic layer, or whatever you need. Good luck and please update me on your validation adventures.