Validation and ASP .NET MVC

A common requirement for web applications is validation. Almost every action follows the same kind of pattern: if the data is invalid, inform the user of the errors and show them the form again. Otherwise, process the request and take them to another page. If you're using ASP .NET MVC, you will notice this kind of code all over the place:

public ActionResult SomeView()
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // Do the real processing here, and 
    // redirect to some other view when 
    // you're done.
}

We all know that duplication is the number one enemy of maintainable code. If there is some way to not have to type in the "ModelState.IsValid" part for 50+% of the methods, then we save ourselves a lot of coding and maintaining. If you think about it, while the validation itself is different from method to method, how to respond to the validation is not. Ideally, we would like to write something like this:

public ActionResult SomeView()
{
    // Do the real processing here, and 
    // redirect to some other view when 
    // you're done.
}

Fortunately, ASP .NET MVC makes it pretty easy to do this.

Prerequisites

  1. ASP .NET MVC
  2. C# 4.0 (for "dynamic")

Step 1: How to do validation on the parameters?

The first step is to figure out how to validate each of the parameters passed into the action. Since validation will be different for each parameter, this means that we have to call some method on some class to perform the actual validation.

public interface IValidator<T>
{
    void Validate(T item);
}

However, we also need a way to tell the controller when some field on the model has an error on it so that it can tell the user. The user might also want to show when the value of some field is valid (maybe draw a green border around the field):

public interface IValidator<T>
{
    void Validate(T item, 
        Action<IValidationError> notifyOnError, 
        Action<IValidationSuccess> notifyOnSuccess);
}

IValidationError and IValidationSuccess are what you would expect:

public interface IValidationResult
{   
    string PropertyName { get; }

    T PropertyValue<T>();
}

public interface IValidationSuccess : IValidationResult
{

}

public interface IValidationError : IValidationResult
{
    string ErrorMessage { get; }
}

Step 2: Does a parameter support validation?

The next step is to figure out whether or not a particular parameter supports validation. If so, then we need a way to get the validator for that parameter. This is pretty simple: we create an interface that all models which support validation will have to implement:

public interface IValidatable<T>
{
    T GetValidator();
}

Step 3: Where to send the user on error?

The next challenge is to figure out where to send the user if there is an error. Since where to send the user might be different for different actions, our controllers will implement a method that will give us the error view given a context:

public interface IValidatableController
{
    ViewResult ErrorViewGiven(ActionExecutingContext context);
}

Step 4: Hooking everything up

The idea is that every time an appropriate action happens, we iterate through all the parameters that are sent. If the parameters support validation, then we get the Validator for the parameter and call the Validate method on it. Once we're done with validating all Validatable parameters, we check to see if there were any errors. If there were errors, we perform the error operation. Otherwise, we continue to the actual method. ASP .NET MVC provides a way for us to hook into the execution of a controller action via the ActionFilterAttribute. The method we are interested in is the OnActionExecuting, which is called -- well -- just before the action is executed.

Steps to take:

  1. Check to see if the controller implements IValidatableController. If not, we have nothing to do.
  2. Setup the error handler, which will add errors via ModelState.AddModelError
  3. For each parameter in the action, find those that implement IValidatable
  4. Get the Validator for each of these parameters to perform the validation.
  5. If there are any validation errors, perform error action. Otherwise, do nothing.

The code:

public class ValidateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!(filterContext.Controller is IValidatableController))
        {
            return;
        }

        var validatableController = filterContext.Controller as IValidatableController;

        Action<IValidationError> errorHandler = x => 
            filterContext.Controller.ViewData.ModelState
            .AddModelError(x.PropertyName, x.ErrorMessage);

        foreach(var parameter in filterContext.ActionParameters)
        {
            if (!shouldValidateParameter(parameter.Value))
            {
                continue;
            }

            dynamic validatable = parameter.Value;
            dynamic validator = validatable.GetValidator();

            validator.Validate(validatable, errorHandler, null);

            if (!filterContext.Controller.ViewData.ModelState.IsValid)
            {
                filterContext.Result = validatableController.ErrorViewGiven(filterContext);
            }
        }
    }

    private bool shouldValidateParameter(object parameter)
    {
        return parameter.GetType().GetInterfaces().Any(x =>
            x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IValidatable<>));
    }
}

Note the use of "dynamic" to get the validator. We need to declare dynamic variables because C# doesn't allow us to cast parameters to their generic types without knowing the actual types. Fortunately, per C# 4.0, we are able to declare variables as dynamic, which causes the code to be late-bound.

How to publish presence in UCMA without using XML

Michael Green published a great article about how to publish presence using UCMA, but it involves using XML. If you hate XML as much as I do, this article is for you.

Note that the code presented here is a snippet and therefore won’t compile/work by itself.

Publishing states

The process for publishing your state is the same for any of the four states (user state, machine state, phone state, calendar state). Let’s look at how to publish the user state:

var userState = new UserState(availability, activity);

_endpoint.LocalOwnerPresence.BeginPublishPresence(
    new PresenceCategory[] { userState },
    PublishPresenceCompleted,
    null);

Wow, that was tough! The other three states have similar classes you can use: MachineState, PhoneState, and CalendarState, all of which are located in the Microsoft.Rtc.Internal.Collaboration namespace. PublishPresenceCompleted is as about as dumb as you might expect:

_endpoint.LocalOwnerPresence.EndPublishPresence(result);

What if you don’t have activity (anything you want it to be)? Don’t pass anything in, and Communicator will show the default activity for your availability.

Publishing device capabilities

Publishing the device capabilities is slightly more involved:

var capability = new DeviceCapability(true, true, true);

var capabilities = new DeviceCapabilities(_endpoint.InnerEndpoint.Uri);

// Set capabilities; i.e., capabilities.Text = capability

var device = new Device(
    _endpoint.InnerEndpoint.Id, _
    new DeviceCapabilities[] { capabilities });

Like the *State classes, Device derives from PresenceCategory. You can view the XML that will be generated by calling PresenceCategory.GetCategoryDataXml() method.

Get all running processes on a remote machine

You know that you can get all the running processes on the local machine with:

Process.GetProcesses();</code

With very little modification, you can also get all the running processes on a remote machine (that you have access to, of course):

Processes.GetProcesses("machine name or IP address here"); 

Deleting non-empty folders from Mac Terminal

If you try to delete a non-empty folder from Terminal on your Mac with

rmdir folder-name

you will get the following error:

rmdir: folder-name: Directory not empty. 

The command you want to use is:

rm -r folder-name

How to rename directories through Terminal

I keep having to rename files and directories in locations outside the purview of my user account. Moving directories into, say, the WebServer folder is easy enough: Mac will ask you for your admin credentials. But, I have not found an easy (read: non-Terminal) way of renaming directories or files once you moved them. So, here is how to do it in the Terminal:

sudo mv -f [original name of the directory] [new name of the directory]

SqlDataReader.GetSqlType() of column

I ran into an interesting problem today that required me to identify the SqlType of any given column, so that I could do special processing where appropriate. It normally wouldn't be such a big deal, because you can call GetType(i) to learn whether it's a string, boolean, DateTime, etc. However, there are some types that need to be differentiated further: for example, money and numbers in SQL might both map to the same .NET type.

Continue reading "SqlDataReader.GetSqlType() of column"

Lambda Expressions

Lambda expressions are a new construct in C# 3.0 that provide a more concise way of writing anonymous methods (which were introduced in C# 2.0). Lambda expressions are especially useful in LINQ statements but can be used any place anonymous methods are.

Continue reading "Lambda Expressions"

Cisco VPN Error 51: Unable to communicate with the VPN subsystem

This error plagues me around every three weeks when I open up my Cisco VPN client on my Mac. The fix for this is straight-forward:

  1. Quit CiscoVPN,
  2. Open Terminal (Applications > Utilities > Terminal), and
  3. Type in the following command:

sudo /System/Library/StartupItems/CiscoVPN/CiscoVPN restart

Type in your password when it asks you for it, and Terminal should restart CiscoVPN, at which point the error should go away.

3 of 19 pages « First  <  1 2 3 4 5 >  Last »

On the Side