Monthly Archives: March 2009
Integrating xVal Validation with Linq-to-Sql
In a previous post I showed you how you can use xVal and the IDataErrorInfo class to add validation to your Asp.net MVC website. In this post I will extend that to Linq-to-Sql and the classes it generates.
The northwind database has a suppliers table. The info contained below is using that table with linq-to-sql.
After adding the table to the designer, a Supplier class gets constructed in the background. This class is a partial class which means we can add to it without changing the code auto-generated in the designer.cs file.
We can then create a partial class called Supplier inheriting from Custom Validation and add a MetadataType attribute to it. This attribute specifies the class for which use for validating the Supplier class.
|
We can then create the SupplierValidation class specifying the properties of the Supplier class we would like to be validated. For instance here I only want to validate the ContactName and the ContactTitle of the Supplier.
|
This specifies that both fields are required and that the ContactTitle cannot be more than 10 characters in length.
The other benefit of using the buddy class here is that if you need to regenerate a table in the linq-to-sql designer, you won’t lose your changes because they’re contained in a separate file.
From here when a Supplier gets passed to in as a parameter on a Controller action it will be validated using the rules in the Supplier Validation class.
Cheers,
Adam
Custom Validation Attributes
In my last post I showed you how to use Validation attributes on your model for validation. Today I will show how you can create your own attributes that can provide custom validation for you.
To accomplish this we need to inherit from ValidationAttribute. We’ll create an attribute today that will only allow a "Yes" or "No" value to be entered. A pretty bad example however it will still suffice.
public class TwoValueAttribute : ValidationAttribute { private string str1 { get; set; } private string str2 { get; set; } public TwoValueAttribute(string str1, string str2) { this.str1 = str1; this.str2 = str2; } public override bool IsValid(object value) { if (value.ToString() == str1 || value.ToString() == str2) return true; else return false; } public override string FormatErrorMessage(string name) { return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString, name, this.str1, this.str2); } } |
All the validation logic takes place in the IsValid function. You can override the FormatErrorMessage function so that you can supply a custom error message for each instance that you want to validate if you so wish.
I’ll show you what I mean.
public class MyOrder : CustomValidation { [TwoValue("Yes","No", ErrorMessage="Only the values '{1}' and '{1}' are possible" + " for the {0} field")] public string Value1 { get; set; } [TwoValue("Yes", "No")] public string Value2 { get; set; } } |
Value2 in the above case will give you the default error message if the value is not "Yes" or "No". However if Value1 is not "Yes" or "No" then the error message supplied will be used and the tokens replaced by the value entered, and the two values used for validation.
And that’s it. Pretty simple really.
Cheers,
Adam
Validation with Asp.net MVC, xVal & IDataerrorInfo
I’ve been playing around with lots of different Validation concepts recently and I think I have come up with something that will be very useful. Hopefully you will also find it useful.
Firstly I have been looking at Steve Sanderson‘s new open source project xVal and decided that it was a good place to start. I was mainly looking at server side validation but xVal can also generate script to enable client side validation.
Firstly I’ll show you how I integrated the DataAnnotations validation pack that Dynamic data uses and is available to us in 3.5sp1 as far as I know.
public class CustomValidation : IDataErrorInfo { string IDataErrorInfo.Error { get { return string.Empty; } } string IDataErrorInfo.this[string columnName] { get { List<ErrorInfo> errors = DataAnnotations.GetErrors(this, columnName).ToList(); return errors.Count > 0 ? errors[0].ErrorMessage : null; } } } |
So I first created a class called CustomValidation that implements the IDataErrorInfo interface. From here only two Properties need to be set, the second one being the most important. Here I use the get property accessor to check the errors for each column and if there are errors I return the error message for the first error.
From here we need our Model that we want validated to inherit from this class as follows.
public class MyProduct : CustomValidation { [Range(0, 100, ErrorMessage="{0} must be set between {1} and {2}")] public double? UnitPrice { get; set; } [StringLength(30)] public string ProductName { get; set; } public List<MyOrder> Orders { get; set; } } public class MyOrder : CustomValidation { [Required] public string Name { get; set; } } |
We can then attach Validation attributes. note: you will need to add a reference to the System.ComponentModel.DataAnnotations assembly for these attributes to show up.
Once we have these two things setup we can start using it. Actually before I start I will show you the DataAnnotations.GetErrors methods.
public static class DataAnnotations { public static IEnumerable<ErrorInfo> GetErrors(object instance) { return GetErrors(instance, null); } public static IEnumerable<ErrorInfo> GetErrors(object instance, string name) { var metadataAttrib = instance.GetType() .GetCustomAttributes(typeof(MetadataTypeAttribute), true) .OfType<MetadataTypeAttribute>().FirstOrDefault(); var buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : instance.GetType(); var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass) .Cast<PropertyDescriptor>(); var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType()) .Cast<PropertyDescriptor>(); var list = from buddyProp in buddyClassProperties join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name from attribute in buddyProp.Attributes.OfType<ValidationAttribute>() where !attribute.IsValid(modelProp.GetValue(instance)) select new ErrorInfo( buddyProp.Name, attribute.FormatErrorMessage(modelProp.Name), instance); if (name != null) list = list.Where(x => x.PropertyName == name); return list; } } |
note: you will need the using statement and a reference to the xVal.dll
using xVal.ServerSide;
Now we can start.
Here is are sample controller actions. Creating a MyProduct and sending it to the view. Then accepting a MyProduct when the form gets posted back.
public ActionResult ValidationTest() { MyProduct p = new MyProduct() { ProductName = "Magazine", UnitPrice = 23 }; p.Orders = new List<MyOrder>(); p.Orders.Add(new MyOrder { Name = "Robert King" }); return View(p); } [AcceptVerbs(HttpVerbs.Post)] public ActionResult ValidationTest(MyProduct product) { if (ModelState.IsValid) { return RedirectToAction("ValidationTest"); } return View(product); } |
In the latter action method the product argument has already been validated by the time it arrives at the if statement. All that needs to be done is to check if the Modelstate is valid and if it is redirect back to the page else display the posted data back to the user with the errors in Modelstate.
Here is what the view looks like.
<h2>Custom Validation</h2> <%= Html.ValidationSummary() %> <% using (Html.BeginForm()) { %> <div style="padding-left: 15px"> <div> Unit Price: <%= Html.TextBox("product.UnitPrice", Model.UnitPrice)%> <%= Html.ValidationMessage("product.UnitPrice")%> </div> <div> Product Name: <%= Html.TextBox("product.ProductName", Model.ProductName)%> <%= Html.ValidationMessage("product.ProductName")%> </div> <div> Orders: <% int j = 0; foreach (MyOrder o in Model.Orders) { %> <div style="padding-left: 15px"> <%= j + 1 %>. <%= Html.TextBox("product.Orders[" + j + "].Name", o.Name)%> <%= Html.ValidationMessage("product.Orders[" + j + "].Name")%><br /> <% j++; %> </div> <% } %> </div> </div> <br /> <input type="submit" /> <% } %> |
Not that this even works when complex binding lists to that have been posted. I’ll try and get a full working example for download here as well if I get enough interest.
And that’s it. In upcoming posts I’ll show you how:
- To do the validation without using the IDataErrorInfo interface and its automatic binding.
- Use the methods that come with xVal to implement some client side validation.
- How to create your own validation attributes.
- Plugging in Castle Validator or other supported validators into xVal.
Hope this helps you get started with Validation in Asp.net MVC.
Cheers,
Adam