The Basics of EF Validation: IDataErrorInfo
When you’re adding or updating data in your database, you really want to make sure that the data being sent to the database is good and true. Often, that’s something that can be checked in the database itself. The first thing you’ll want to do is make sure that the database has validation constraints set on the columns, like nullability or max data sizes. If you’re going EF model-first, you can set these properties on the properties of your entities. If you’re not, you can set these properties in the database or get even fancier and write triggers that check the validity of the data. Finally, you can disable insert, update and delete altogether in favor of stored procedures, changing your EF mapping to generate calls to those instead. The nice thing about checks in the database is that no matter how the data gets there, whether it’s via your EF-based app or not, the checks happen.
However, if you’d like to also put checks into your EF code, perhaps because you’d like to avoid a round-trip to the database for bad data, you can do so in your EF-enabled language of choice, e.g. C#.
Imagine a very simple EDM to describes web advertisements:
All properties on our entity type get a generated On<
namespace EdmTest { partial class Ad { partial void OnLinkChanging(string value) { if (!value.StartsWith("http://",Here we’ve made sure that whenever we set the Link property, it must be of a certain format. If we were to violate that restriction, things go boom:
StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentOutOfRangeException("Link must start with 'http://'"); } } } }
As MVC translates the form fields into values on the Ad object that is passed to the controller’s Create method, setting the Link property with a bad value triggers the exception:
// POST: /Ad/Create [HttpPost] public ActionResult Create(Ad ad) {...}
MVC catches the exception before the Create method is even called and the view shows the error message. Notice that the error message is not what we provided, however.
Further, sometimes there are problems on an object’s state that span more than one property. Unfortunately, because such a constraint can’t be checked on any single property change, we need to at least check it at the object level, not just at the property level.
For both of these issues, we have IDataErrorInfo.
IDataErrorInfo
The IDataErrorInfo interface was introduced back in the mists of time with .NET 1.x for use specifically with data binding in Windows Forms. I wouldn’t recommend that anyone invest in anything but maintenance on their WinForms apps, but ASP.NET, the Windows Presentation Foundation (WPF) and Silverlight all support IDataErrorInfo . Data binding is involved enough and GUI-framework-specific enough that you’ll need to read up on it in your favorite GUI-framework-specific book, but the IDataErrorInfo interface is exactly what we need even without data binding:
namespace System.ComponentModel { public interface IDataErrorInfo { string Error { get; } string this[string columnName] { get; } } }
Notice that IDataErrorInfo exposes error descriptions at both the object and the property/column level. It’s easy to implement this standard interface on our example Ad class:
partial class Ad : IDataErrorInfo { public string Error { get { // Check the ad for errors if (string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(ImagePath)) { return "Must set Title or ImagePath"; } return null; } }
public string this[string columnName] { get { // Check any specific property for errors switch (columnName) { case "Link": if (Link != null && !Link.StartsWith("http://",
StringComparison.InvariantCultureIgnoreCase)) { return "Link must start with 'http://'"; } break; } return null; } } }
Because each of the generated entity classes is partial, you can provide your own implementation to be merged with the generated implementation, in our case the IDataErrorInfo interface implementation. Now, when MVC hydrates an object that implements IDataErrorInfo, it’ll check to see if there are problems. To check, our controller provides the ModelState property, which itself provides the IsValid flag:
// POST: /Ad/Create [HttpPost] public ActionResult Create(Ad ad) { try { if (!ModelState.IsValid) { return View(); } ... } catch { return View(); } }
// POST: /Ad/Edit/ [HttpPost] public ActionResult Edit(Ad ad) { try { if (!ModelState.IsValid) { return View(); } ... } catch { return View(); } }
In addition to the IsValid flag, the ModelState provides a list of property name/error message pairs that show in the code that the view generator spits out when it’s creating forms, e.g.
... <% using (Html.BeginForm()) {%> <%: Html.ValidationSummary(true) %> ... <%: Html.TextBoxFor(model => model.Link) %> <%: Html.ValidationMessageFor(model => model.Link) %> ...
It’s in the ValidationSummary helper that shows object-level errors and the ValidationMessageFor helper that shows property-level errors:
There are other means of validation that you will want to investigate, like the validation attributes supported by MVC and SilverLight, but IDataErrorInfo is the one with the broadest reach. It’s also the one that’s simplest for you to check yourself if you’re not getting the automatic support you want from your GUI framework. For example, because the SaveChanges method on the context base class is virtual and because we’ve got the Object State Manager, we can check ourselves for object errors using IDataErrorInfo:
using System.Data.Objects; namespace AdMan.Models { partial class sbdbEntities { public override int SaveChanges(SaveOptions options) { // Make sure we're detecting all changes. base.SaveChanges // does this, but that may be too late. DetectChanges();
// Get all the new and updated objects var objectsToValidate = ObjectStateManager. GetObjectStateEntries(EntityState.Added | EntityState.Modified). Select(e => e.Entity).OfType<IDataErrorInfo>();
// Check each object for errors foreach (var obj in objectsToValidate) { // Check each property foreach (var property in obj.GetType().GetProperties()) { var columnError = obj[property.Name]; if (columnError != null) { throw new Exception(columnError); } }
// Check each object var objectError = obj.Error; if (objectError != null) { throw new Exception(objectError); } }
// All clear return base.SaveChanges(options); } } }
Here we’re overriding the SaveChanges method we’ve been calling all this time to take advantage of the IDataErrorInfo interface. The first call to DetectChanges is to make sure we’ve gotten all the changes into the Object State Manager (only necessary if you’re using EF POCO classes). The call to the GetObjectStateEntries method on the ObjectStateManager class produces all of the added and modified objects, their state, what the old and new values are, etc. We pull off each one of the entities that implement IDataErrorInfo and call the methods to check for property and object-level errors. If we find one, we throw an exception, otherwise we let the call to SaveChanges through.
This code isn’t needed if you’re already using a GUI framework that supports IDataErrorInfo, but it’s still handy to know you can roll your own code into SaveChanges if you need to.
Where Are We?
IDataErrorInfo is the core of data validation support in GUI libraries since .NET 1.x and while there are simpler ways to do it for individual libraries, IDataErrorInfo works just fine with MVC and EF, two of the most popular GUI libraries we’ve got just now.