ASP.NET MVC [Remote] validation – what about success messages?

January 27th, 2011 by Gareth / Comments

Since ASP.NET MVC 3 it’s easy to validate an individual form field by firing off a HTTP request to a controller action. This action returns either Json(true) if the value is valid or a Json(“error message”) if it is not valid. If you return an error then jquery.validate.js will add your error message to the validation element for the field being validated. It’s easy to setup on your view model :

	[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
[EmailAddress]
[Remote("RemoteValidation_UniqueEmailAddress", "Account")]
public string Email { get; set; }
	

When the user makes a change to the Email field the /Account/RemoteValidation_UniqueEmailAddress action is automatically called on the server. The server side action is just as simple to setup :

	public ActionResult RemoteValidation_UniqueEmailAddress( string Email)
{
   object oResult = false;
   if( Validation.IsEmailAddress( Email) == false)
   {
      return Json( "Please enter a valid email address", JsonRequestBehavior.AllowGet);
   }
   else if( m_oMembershipService.IsThisEmailAlreadyTaken( Email) == true)
   {
      return Json( "This email address is already in use", JsonRequestBehavior.AllowGet);
   }
   else
   {
      return Json( true, JsonRequestBehavior.AllowGet);
   }
}
	

A remote validation error is displayed in exactly the same way as a view would render it. This is because the javascript latches onto the Html.ValidationMessageFor element you setup in your view :


This is ok isn’t it? But…

I want the Email Address label to be bold and red too

In the interests of best practice web form techniques I want the email address label to be bold and red, as well as the error message. To do this I had to hack jquery.validate.js so that it calls a local javascript function with the same name as the server function (with “_response” appended to it) :

	remote: function(value, element, param) {
    if ( this.optional(element) )
        return "dependency-mismatch";
 
    var previous = this.previousValue(element);
    if (!this.settings.messages[element.name] )
        this.settings.messages[element.name] = {};
 
    previous.originalMessage = this.settings.messages[element.name].remote;
    this.settings.messages[element.name].remote = previous.message;
 
    param = typeof param == "string" && {url:param} || param;
 
    if ( previous.old !== value ) {
        previous.old = value;
        var validator = this;
        this.startRequest(element);
        var data = {};
        data[element.name] = value;
 
        $.ajax($.extend(true, {
            url: param,
            mode: "abort",
            port: "validate" + element.name,
            dataType: "json",
            data: data,
            success: function(response) {
 
                //START OF HACK
                var aParts = param.url.split(/\//);//eg; param.url = "/Account/RemoteValidation_UniqueEmailAddress"    ELMS
                oCustomResultFunction = aParts[aParts.length - 1] + "_response";//ELMS
                //END OF HACK
 
                validator.settings.messages[element.name].remote = previous.originalMessage;
                var valid = response === true;
 
                if ( valid ) {
                    var submitted = validator.formSubmitted;
                    validator.prepareElement(element);
                    validator.formSubmitted = submitted;
                    validator.successList.push(element);
 
        //START OF HACK
        var bShowErrors = true;
        if( typeof(window[oCustomResultFunction]) == "function")
        {
            bShowErrors = window[oCustomResultFunction]( true, null, validator);
        }
        //END OF HACK
 
        if( bShowErrors)//HACK
        {
            validator.showErrors();
        }
 
        }
        else
        {
            var errors = {};
            var message = (previous.message = response || validator.defaultMessage( element, "remote" ));
            errors[element.name] = $.isFunction(message) ? message(value) : message;
 
            //START OF HACK
            var bShowErrors = true;
            if( typeof(window[oCustomResultFunction]) == "function")
            {
                bShowErrors = window[oCustomResultFunction]( false, errors, validator);
            }
            //END OF HACK
 
            if( bShowErrors)//HACK
            {
                validator.showErrors(errors);
            }
        }
 
        previous.valid = valid;
        validator.stopRequest(element, valid);
    }
}, param));
        return "pending";
    } else if( this.pending[element.name] ) {
        return "pending";
    }
    return previous.valid;
},
	

And now a client side javacsript function called RemoteValidation_UniqueEmailAddress_response will be called with the result of the remote validation. In this example I know that the function only targets the Email field but it’s wise to check the errors collection that comes back in case other model errors were added in the server function (see the outcommented code below). The aErrors collection is keyed on the client side ID of the invalid field/s :

	function RemoteValidation_UniqueEmailAddress_response( bIsValid, aErrors, oValidator)
{
    if( bIsValid == false)
    {
        /* This bit is optional
        for( var sId in aErrors)
        {
            $("label[for=" + sId + "]").addClass( "error").css( "font-weight", "bold");
        }*/
 
        $("label[for=Email]").addClass( "error").css( "font-weight", "bold");
    }
    else
    {
        $("label[for=Email]").removeClass( "error").css( "font-weight", "normal");
    }
 
    return true;
}
	

The first argument to the function is whether there is an error or not. The second parameter is the errors collection. Now when I enter an invalid email address my form looks like this

That’s better but something is still bothering me

Success messages would be nice

As well as catering for validation errors you now get the chance to provide success feedback as well which can be just as important as error feedback. For example if the user is registering on a site and choosing a username the validation could check that the username is available. If it is then you can present a jolly green tick to the user. This completely removes ambiguiuty, especially if the previous username they entered was already taken and they saw a validation error

One last bit of code :

	var g_bEmailHasBeenInErrorState = false;
function RemoteValidation_UniqueEmailAddress_response( bIsValid, aErrors, oValidator)
{
    if( bIsValid == false)
    {
        $("label[for=Email]").addClass( "error").css( "font-weight", "bold");
        g_bEmailHasBeenInErrorState = true;
    }
    else
    {
        $("label[for=Email]").removeClass( "error").css( "font-weight", "normal");
 
        if( g_bEmailHasBeenInErrorState)
        {
            g_bEmailHasBeenInErrorState = false;
 
            $("#Email").after( "<img style='float:left;' src='<%=Url.Content( "~/Content/img/tick.gif")%>' alt='This is a valid email for this web site' />");
        }
    }
 
    return true;
}
	

Hang on a minute. Why isn’t this functionality in jquery.validate.js anyway? Go to your room!

I didn’t refer once to AJAX because :

  • XML is moribund everywhere but the enterprise.
  • IE had javascript HTTP requests before the web existed.
  • Prior to that you could just screen scrape a hidden iframe for crying out loud!

ASP.NET MVC, jQuery, UI

Comments

MongoDB and ASP.NET MVC – A basic web app

January 16th, 2011 by Gareth / Comments

I first heard about document databases (NoSql) last year thanks to Rob Conery’s blog post. At the time I couldn’t imagine a database without SQL but there really is such a thing and MongoDB is a leading example. Today I’ve spent some time working with MondoDB putting together a basic ASP.NET MVC web application to CRUD a bugs database.

A document database differs from a relational database in that, when you can get away with it, a record (known as a document here) can contain all it’s own data. It is not necessary to have normalised tables with joins pulling data together into a result set. Instead of having an order table, a product table, a customer table, an address table etc… you can have a single record containing all this information.

Here is an example of a single record for a user :

	{
    "name": "Mr Card Holder",
    "email": "blowJoggs@myemail.com",
    "bankAccounts":
    [
        {
            "number": "12332424324",
            "sortCode": "12-34-56"
        },
        {
            "number": "8484949533",
            "sortCode": "11-33-12"
        },
    ],
    "address":
    {
        "address1": "100 The Big Road",
        "address2": "Islington",
        "address3": "London",
        "postCode": "LI95RF"
    }
}
	

And that’s how the record is stored in the database; the exact opposite of a normalised database. The user’s bank accounts are stored in the user document. In this example a user’s bank accounts are unique to that user so it’s ok to store them right there. But what about when the associated information needs to be more centralised such as an author’s list of published books? What happens when there are multiple authors on a book? In this case you would have a separate collection for the books and put the list of book ObjectIds in the author document. Like this :

	{
    "name": "Mr Author",
    "email": "I_write_books@myemail.com",
    "publishedBooks":
    [
        {"id": "12332424324"},
        {"id": "5r4654635654"},
        {"id": "5654743"}
    ],
    "address":
    {
        "address1": "142 The Other Road",
        "address2": "Kensington",
        "address3": "London",
        "postCode": "L92RF"
    }
}
	

After loading the author from MongoDB you would then perform a second query to load the associated books.

The data is stored in BSON format (a binary format of JSON). You still get indexes and the equivalent of where clauses, the simplicity is not at the cost of functionality. Pretty much the only thing you don’t get are complex transactions.

I wanted to try this out for myself so I installed MongoDB and saw it working from the command line and then started looking into a C# driver. After a quick look around the internet it seems that there are two main options for C# drivers :

I chose the official driver because, although it isn’t as mature as Sam Corder’s driver, I’d already started reading the docs for it.

A day in Visual Studio and a JSON Viewer

First I wrote a very rudimentary membership provider that uses the Mongo database. This lets you register on the site and then login which is enough for my test. Hopefully the MongoDB guys will release a complete ASP.NET membership provider soon.

Secondly I wrote an ASP.NET MVC controller, domain class and MongoDB bug repository to help me figure out how to work with a Mongo database using C#. The BugRepository class contains database code like this :

	public Bug Read( string sId)
{
    MongoServer oServer = MongoServer.Create();
    MongoDatabase oDatabase = oServer.GetDatabase( "TestDB");
    MongoCollection aBugs = oDatabase.GetCollection( "bugs");
 
    Bug oResult = aBugs.FindOneById( ObjectId.Parse( sId));
 
    return oResult;
}
 
public Bug Update( Bug oBug)
{
    MongoServer oServer = MongoServer.Create();
    MongoDatabase oDatabase = oServer.GetDatabase( "TestDB");
    MongoCollection aBugs = oDatabase.GetCollection( "bugs");
 
    aBugs.Save( oBug);
 
    return oBug;
}
	

If, like me, you are just getting started using MongoDB with C# you may find it useful to see the code so here is the source code for the test app. You’ll need Visual Studio 2010 and MVC 3. This is what you’ll get :

My main resources while figuring this out were :

One more thing. Document databases are absolutely perfect for content management systems. One of the challenges for a CMS is handling dynamic schemas and in relational databases this usually involves at least two generic looking tables. To a document database the collection schema is a trivial concept, it just doesn’t care! You can add new properties to a collection whenever you feel like it, even when there is huge amount of data already there. Drupal has already integrated MongoDB I expect more to follow.

ASP.NET MVC, MongoDB

Comments

My desk

January 9th, 2011 by Gareth / Comments

I am always fascinated when I see photos of other developers’ desks so I thought I’d share mine with the world. For years my desk has been an undervalued dump and, in an act of shameful correlation, I have been an under performing stig for years too.

You’ll see plenty of web design books there which I should put away, I don’t read them any more. There are some iPhone developer books and a few .NET books as well as some productivity and business books. I do have 4 Android books (of the brilliant Mark Murphy Commonsware series) but they’re all pdfs so I can’t put them on my shelf except as represented on a usb drive. I’m waiting for the perfect tablet to come out so I can read them comfortably away from my desk. At the moment I make do with the iPod Touch (brilliant device!).

I also have a few books that provide inspiration but for me the best inspiration is this :

  1. Browse to Google maps
  2. Go to an area where I would love to live
  3. Search for “house for sale”
  4. Look at the photos of my dream house, and dream

Try this example of Bridgnorth. Makes me work harder and visualize my goal. I can see us being very happy in a new bigger house away from this poky end terrace with zero parking although Bridgnorth might be a bit too remote for my other half

I’m in the process of rearranging this the tiniest room in the house (otherwise known as my office). so I’ll blog again when that happens.

Inspiration, Productivity

Comments

My Micro-SAAS Journey

Get the inside story as I work out how to create a micro-SAAS