Archive for the ‘ASP.NET MVC’ Category

ASP.NET MVC – A Declarative Model Validation Technique

18 Sep

I have been doing a lot of work lately with ASP.NET MVC and found that many of the MVC examples are set up where the server that the web application runs has direct access to a database and use LINQ to SQL style validation. See Nerd Dinner example to see how the Microsoft guys validate.

My application cannot use the Nerd Dinner example because my front end IIS server does not have direct access to a database (Or in my case CICS mainframe!). So I needed a simple declarative way of validating my data before I sent it off to a web service to be inserted into the mainframe.

So, using attributes, reflection and a little help from a colleague of mine, I came up with this solution. The main algorithm in the solution is an extension method that attaches itself to the FormCollection class used by MVC. The method iterates through each method attribute attached to the properties of a given class. Inside each attribute is a validation technique that is applied to the data stored in the FormCollection, if the validation fails a model error is added to the model state.

public static void Validate(this FormCollection fc, Type t, ModelStateDictionary msd)
{
    foreach (PropertyInfo pi in t.GetProperties())
    {
        foreach (ValidatorAttribute attr in pi.GetCustomAttributes(typeof (ValidatorAttribute), true))
        {
            msd.SetModelValue(attr.Name, fc.GetValue(attr.Name));
            //ignore attributes that are not in the value list
            bool attrInValueList = (from key in fc.Keys.Cast<string>()
                                    where key.Equals(attr.Name)
                                    select key).Count() > 0;
            if (attrInValueList && !attr.Validate(fc))
                msd.AddModelError(attr.Name, attr.ErrorMessage);
        }
    }
}

As mentoned, the validation algorithm uses attributes attached to the properties of a model. The validation attributes are simple static classes that are initialised with the name of the property, an error message to display and any extra parameters that are necessary. Below is an example of regular expression and date validation attributes.

public abstract class ValidatorAttribute : Attribute
{
    protected ValidatorAttribute(string name, string errorMessage)
    {
        Name = name;
        ErrorMessage = errorMessage;
    }
    public string Name { get; set; }
    public string ErrorMessage { get; set; }
    public abstract bool Validate(FormCollection fc);
}
public class RegexValidatorAttribute : ValidatorAttribute
{
    public RegexValidatorAttribute(string name, string regex, string errorMessage)
        : base(name, errorMessage)
    {
        Regex = regex;
    }
    public string Regex { get; set; }
    public override bool Validate(FormCollection fc)
    {
        if (fc[Name] == null)
            return false;

        return new Regex(Regex).Match(fc[Name].Trim()).Success;
    }
}
public class DateValidatorAttribute : ValidatorAttribute
{
    public DateValidatorAttribute(string name, string errorMessage)
        : base(name, errorMessage) { }
    public override bool Validate(FormCollection fc)
    {
        DateTime d;
        return DateTime.TryParse(fc[Name], out d);
    }
}

Now all I did was apply these validation attributes to the properties of my model that I wanted to validate. A big note here is that the name passed as a parameter to the validation attribute must match the name of the property to be validated.

public class Client
{
    [RegexValidator("FirstName", @"^([a-zA-Z0-9\s\-]{0,12})$", "Please enter a valid first name.")]
    public string FirstName { get; set; }

    [RegexValidator("FamilyName", @"^([a-zA-Z0-9\s\-]{0,18})$", "Please enter a valid family name.")]
    public string FamilyName { get; set; }

    [DateValidator("DateOfBirth", "Please enter a valid date of birth.")]
    public string DateOfBirth { get; set; }
}

Finally, to validate your form collection all you need to do is the following. Again the keys in the form collection must match the properties of the model that is being validated.

collection.Validate(typeof(Client), ViewData.ModelState);
if (ViewData.ModelState.IsValid)
    UpdateModel(client, collection.ToValueProvider());

That’s it! Now I can clearly see in one location, the model, what the validation requirements of each property are and easily adjust them if need be. The same validation algorithm and validation attributes can be used for any model!

 
 

ASP.NET MVC Custom Helper – A HttpRequest extension method

31 Aug

Recently I ran into an issue using JQuery’s AJAX library and ASP.NET MVC. Refer to my last post about using JQuery’s AJAX with ASP.NET MVC.

After deploying a solution to my web server I had a major issue where my JQuery $.post method was not working and was not giving me any errors. I immediately thought that there was an error in my server settings or something other then my code because I had no issues in my development environment.

It turned out to be an issue with routing! In my development environment the application was installed under the root directory (‘\’) where as on my server it was installed under a subfolder of the root directory (For example: ‘\test\’) thus the value I coded inside my $.post method was incorrect when deployed to my server.

In order to make it easy for me to deploy to any environment without changing my JQuery method each time I created this simple extension method for the HttpRequest class.

public static class HttpRequestHelper
{
    public static string AppendServerPath(this HttpRequest request, string path)
    {
        return request.ApplicationPath.EndsWith("/")
                   ? request.ApplicationPath + path
                   : request.ApplicationPath + "/" + path;
    }
}

So now inside my JQuery method all I have to do is call this method and append the exact path I wish to invoke.\

$.post("<%=Request.AppendServerPath("Test/Controller/DoSomething") %>", null, doSomeThing());

This also worked well inside my Site.Master where I ran into similar issues linking images, style sheets, JavaScript files, etc.

Wherever there is a link in my code I now call this extension method and know it will deploy almost everywhere.