LA.NET [EN]

Feb 19

On these last posts, we’ve seen how we can automatically fill an object from data submitted by a form. today, we’re going to take a look at how you can use the IDataErrorInfo and get automatic validation support. The first thing you should know is that IDataErrorInfo isn’t defined by the MVC fraemework. In fact, this is an existing interface defined on the System.ComponentModel namespace and it only exposes two read-only properties: Error and Item. Error returns an error which indicates what’s wrong with this object and Item returns an error message for a specific property of that object (ie, when that property doesn’t hold a valid value).

Theoretically, reusing this interface means that you’re able to reuse the interface and get validation across several windows platforms (WPF, windows forms and ASP.NET MVC). In order to get things going, we’re going to reuse the User class introduced in a previous post and we’ll add validation to it. Here’s how the class looks like after we’ve added the interface to its list of implemented interfaces:

public class User: IDataErrorInfo {
    public Int32 UserId { get; set; }
    public String Name { get; set; }

    public string this[ String columnName ] {
        get  {
            if (!ColumnNameIsValid(columnName)) return null;
            return ValidateProperty( columnName );
        }
    }

    private String ValidateProperty( string propertyName ) {
        // real world you”d probably cache this info…could probably use an existing
        //framework for validation and cache the results so that you don”t have to get
        //this info for all the properties
        if( propertyName.Equals( "Name", StringComparison.CurrentCultureIgnoreCase  )) {
            return String.IsNullOrEmpty( Name ) ? "Name cannot be empty" : null;
        }
        return UserId <= 0 ? "UserId must be a positive number" : null;
    }

    private Boolean ColumnNameIsValid( String name ) {
        return new[] {"UserId", "Name"}.Any( prop => prop.Equals( name, StringComparison.CurrentCultureIgnoreCase ) );
    }

    public string Error {
        get {
            String[] propNames = {"UserId", "name"};
            var errors = propNames.Select( prop => ValidateProperty( prop ) );
            return errors.COUNT() as Computed == 0 ? null : "The object is not in a valid state";
        }
    }
}

Since this is demo code,I’m just hard coding the validation rules and using some simple simple rules (Ids must be positive and Names cannot be empty). As you can see,the Item property checks to see if the name of the property is valid and, if it is, it tries to validate its current value. When the value isn’t valid, we return an error message with information about the error. As we’ve seen, the Error property should return an error message if the object is in an invalid state. Now, we should probably change the markup for the view so that it is able to give error messages when something goes wrong:

<% using( var frm = Html.BeginForm("UpdateUser", "Home")) {%>
  <label for="userid">User id:</label>
  <%= Html.TextBox( "userid" ) %>
  <%= Html.ValidationMessage( "userid"  ) %>
  <br />
  <label for="name">Name:</label>
  <%= Html.TextBox( "name" ) %>
  <%= Html.ValidationMessage( "name"  ) %>
  <br />
  <input type="submit" value="Confirm" />
  <br />
  <%= Html.ValidationSummary( ) %>
<% } %>

As you probably recall, we’re adding the validation helpers in order to get feedback whenever there’s an invalid value on a property of the object. Finally, we need to update the UpdateUser action so that it won’t redirect to a new page if the information submitted in the form leads to an object in an incorrect state. Here’s a possible implementation of that method:

public ActionResult UpdateUser(){
    var user = GetUserFromSomewhere();
    if( !TryUpdateModel( user ) ) {
        return View( "User", user );
    }
    return View( "ShowInfo", user );
}

If you compare the previous snippet with the code we had in the other post, you’ll notice that we’re using the TryUpdateModel (instead of the UpdateModel). The reason is simple: with UpdateModel, we would get an exception if there was any validation errors (ie, you’d have to wrap the UpdateModel call in a try/catch block since you’d probably be interested in showing the form to the user again). So, with the previous code, here’s what you’ll be getting when you pass a negative user id and an empty name:

error

As you can see, you end up getting error information for each property that has an incorrect value and, since we’ve added a summary, we’re also getting the general error message returned by the Error property. It’s now time to take a peek at how validation is implemented internally.

If your model object implements this interface, then the default binder will call the Item property for each property that is set to see if its value is valid. After that, it will try to get an error message for the object itself by accessing the Error property. When any of these properties return an error, that info is added to the current ModelState so that it’s available to the view. It’s also interesting to point out that validation is performed on the virtual OnPropertyValidated and OnModelUpdated methods (if you extend the DefaultModelBinder class and override these methods, then don’t forget to call the base methods or you won’t have any validation support).

Ok, now that you know how things work  under the hood, you can probably see that the code  I’ve used in this example might not really be the most performant code you could have. To start with, we can probably agree that returning null for the Error property is probably the best option since we’re running validation all over again on that property’s body. However, there probably are some scenarios where you have dependencies between properties and you’d probably be interested in running validation when your object is completed hydrated. So, you could return null from the Item property, but then you’d only be able to return a string from the Error property (which wouldn’t be good for reporting several errors).

I guess that the bottom line is that  you can probably use this validation mechanism for basic validation (ex.: ensuring that a field has n chars or that the entered date is valid) but it’s really not that usable for real complex validations. Again, only my opinion.

Keep tuned for more on the MVC framework.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>