MVC 3 Unobtrusive Validation with LINQ to SQL
Recently I've been working on a small project that uses LINQ to SQL as it's data layer (it's a very simple model) and contains two presentation projects - a web form application for the front end and an MVC 3 administration panel.
When playing with the unobtrusive validation engine, you'll find many examples on the 'net telling you to decorate your model with various attributes from the System.ComponentModel.DataAnnotations namespace. Now, I ran in to a bit of an issue with the LINQ to SQL objects - they're generated dynamically. I could add the attributes, but they would be overwritten any time I made a change to my DBML file. So, after a bit of searching I came across the MetaDataTypeAttribute in the same namespace. This attribute allows you to create a completely separate, standard class and define it as a Meta Data container for another class. Neat, hu?
Well, I quickly ran in to my second issue - I could create my separate class, but I still had to tag the LINQ classes with the attribute... which meant that data would also be overwritten.
LINQ classes are partial classes - it didn't dawn on me for a few hours, but I could effectively create another partial class in the DBML's code behind file (right click on the DBML file -> View Code) and decorate THAT class with the attribute - which would never be overwritten by the code generator. Below is what my LINQ code behind looks like. This example also includes a CustomValidationAttribute example, which took me a little bit to figure out:
1: using System;
2: using System.Text;
3: using System.ComponentModel.DataAnnotations;
4:
5: namespace MyNameSpace {
6: [MetadataType(typeof(News_Validation))]
7: partial class News {
8: }
9: [MetadataType(typeof(ExclusionDate_Validation))]
10: partial class ExclusionDate {
11: }
12: public sealed class News_Validation {
13: [Required(ErrorMessage = "Post content is required.")]
14: public string Content { get; set; }
15: }
16: public sealed class ExclusionDate_Validation {
17: [Required(ErrorMessage = "'Date To Replace' is required."), CustomValidation(typeof(Validator), "ValidateDate")]
18: public DateTime DateToReplace { get; set; }
19:
20: [CustomValidation(typeof(Validator), "ValidateDate")]
21: public DateTime ReplacementDate { get; set; }
22: }
23:
24: public static class Validator {
25: public static ValidationResult ValidateDate(string inputDate) {
26: DateTime thisDate;
27: if ((inputDate != null) && inputDate.Trim() != string.Empty) {
28: if (DateTime.TryParse(inputDate, out thisDate)) {
29: return ValidationResult.Success;
30: } else {
31: return new ValidationResult("Not a valid date.");
32: }
33: } else {
34: return ValidationResult.Success;
35: }
36: }
37: }
38: }