Hey gang, just a quickie today. I have been working recently on setting up server side and client side model validation “by the book” in ASP.NET MVC 2 complete with data annotations and the first question that hit me when I was declaring attributes on my LINQ to SQL partial classes to determine model validation was “hold on, surely there is a way to automagically have MVC infer my validation rules based on my database schema…”, it seemed a little silly to me that I would have to re-declare validation rules on my model classes when I have a perfectly, well-decorated set of rules within SQL Server, not to mention within the LINQ to SQL .dbml designer file, against my database objects. I also felt this was going against the DRY principle which is “Don’t Repeat Yourself”.
So, what if you could avoid the monotonous, not to mention need for duplicated code and have your model automatically validation based on your LINQ to SQL schema? (which in turn was resolved from the database.) and what would you expect it to do? Well we know when we declare VarChar‘s, NVarChar‘s and other text types in the database we specify a length in a fashion of VarChar(100) right? Well, this information serves for what our [StringLength] attributes could be.
The other, and probably more pressing check would be if we could automate our [Required] Attribute so we could determine if the field is required by the user for input. Well, thankfully our LINQ to SQL schema reveals that too; Let’s see how this could come together…
Firstly, the main idea and code skeleton for this approach came from Carl over at “Carl On Development” as he displayed quite an ingenious solution to the problem; His approach was to simply check the actual LINQ to SQL data type of the column of the class in question as a pre-validation check to address the field length problem, once having received the value stored in the brackets of VarChar(100) from the LINQ to SQL model, build a standard StringLengthAttribute class returning this newly sought value, the same as if you were to attribute your property as [StringLength(100)] yourself!
Unfortunately, his text editor appeared to have removed the “<” and “>” signs which made it difficult to determine what generic types he was calling upon in his function. However, after a bit of playing, I worked out what was going on and also then thought this could be taken further, and introduced another test for “Is Null” testing.
Enjoy, and tell me what you think (Remember, as this inherits from the AssociatedValidatorProvider class in the System.Web.Mvc namespace, there is no need to decorate any of your model, or partial model classes with any attributes, meaning no more work is required – the thing will just work once it is registered!):
public class SchemaValidatorProvider : AssociatedValidatorProvider
{
public static readonly string[] TextTypes = new string[] { "char", "varchar", "nchar", "nvarchar" };
public static readonly string LengthRegex = "[" + string.Join("|", TextTypes) + "]\\((.*)\\)";
public bool IsNotNull(ColumnAttribute column)
{
return !column.CanBeNull;
}
public int? ExtractLength(ColumnAttribute column)
{
var match = Regex.Match(column.DbType.ToLower(), LengthRegex, RegexOptions.Compiled);
if (match == null || match.Groups.Count != 2) return null;
return string.Compare(match.Groups[1].Value.ToLower(), "MAX", true) == 0 ? int.MaxValue : int.Parse(match.Groups[1].Value);
}
protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
{
var columnAttribute = attributes.OfType().FirstOrDefault();
if (columnAttribute == null || columnAttribute.DbType == null)
yield break;
// Determine max length of column and yield the StringLength attribute if found.
var maxLength = ExtractLength(columnAttribute);
if (maxLength.HasValue)
yield return new StringLengthAttributeAdapter(metadata, context, new StringLengthAttribute(maxLength.Value));
// Determine if state of column is not null, if so yield Required attribute.
if (IsNotNull(columnAttribute))
yield return new RequiredAttributeAdapter(metadata, context, new RequiredAttribute());
}
}
Then over in your Global.asax.cs:
public class MvcApplication : System.Web.HttpApplication
{
....
protected void Application_Start()
{
ModelValidatorProviders.Providers.Add(new SchemaValidatorProvider());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
...
}
}
Look Ma! Nothing required on the model, it just works!
[MetadataType(typeof(ShareMetadata))]
public partial class User
{
private sealed class UserMetadata
{
// [Required] [StringLength(50)] - No longer required!
[DisplayName("The name")]
public string Name { get; set; }
[Range(1, 100)] // Still might be required depending on your business requirements!
[DisplayName("Cost Per Unit ($)")]
public decimal UnitCost { get; set; }
}
}
What this means is you will not have to declare any “typical” validation rules on your model classes at all, required fields and field length will be completely handled for you automatically without the need for any Data Annotation Attributes!
Hopefully this will cover both validation cases I could think of initially using the in-built, and very extensible ASP.NET MVC validation providers. I would be keen to know if this could be extended further for other checks.
VN:F [1.9.10_1130]
Rating: 4.0/5 (7 votes cast)
VN:F [1.9.10_1130]
Rating: +3 (from 3 votes)