Jimmy Bogard's recent blog post entitled Cleaning up POSTs in ASP.NET MVC was a great read. But I wanted to share another method that I have used with great success in recent months. One of the primary issues that Jimmy is trying to solve in his blog post is that of DRYing up duplicate code by creating application-level infrastructure code that can easily be used and varied within certain boundaries.
According to REST-based principles and general best practices in web programming, a GET request issued to a server should not change application state. It's only issuing a query. The code to serve this kind of request is simple enough—just go and get the data. But a POST (and a PUT for that matter) is another story entirely. In this case, the user is instructing us to invoke some mutating behavior. According to the pattern, the basic steps are:
- The user enters data into a form and clicks submit.
- The browser POSTs the data to the web server.
- The model binders facilitate validation of the incoming data for correctness, e.g. are all of the fields populated with reasonable values?
- If the proper input values are incorrect or missing, the controller returns an error view to the user.
- If the input passes all validation, the controller invokes the desired behavior and redirects the user to a success page.
All in all, it's a relatively simple pattern and one that is used with much success all over the web in virtually all web frameworks.
Interestingly enough, one of the reasons for the POST-Validate-Redirect pattern has to do with default browser behavior. For example, if we didn't redirect a user and instead just showed a view, a user refreshing that page would get that wonderfully confusing "Do you want to re-post this data?" message. Hence the redirect at the end to avoid the problem altogether. Typically we redirect users to a page where they can see the results of their operation so that they know its successful.
But what if your views are updated asynchronously? In this case, wouldn't it be confusing to your user if you immediately redirected them to a page that supposedly contained the results of the operation but the views were not yet updated with the results of the operation? And what if we wanted to submit data to the server without making the user leave the current page?
Enter a new paradigm: Ajax.
[CAVEAT: I realize that this requires Javascript to be enabled within the browser. I've decided that I'm not catering the .5% to 1% of people on the web who disable Javascript. There are too many benefits. Also note also that SEO practices are not relevant here because the user is submitting data to the server.]
One of the techniques that has worked wonders for me is facilitated by Ajax requests. The general idea is something like this:
- The users enters data into a form and clicks submit.
- The browser POSTs the data to the web server using an Ajax request (typically through jQuery).
- The model binders facilitate validation of the incoming data for correctness, e.g. are all of the fields populated with reasonable values?
- If the proper input values are incorrect or missing, return a JSON object to the browser containing "model state errors".
- If the proper input values are correct, invoke the appropriate behavior (which usually involves dispatching a message).
- Return HTTP 200 to the browser.
- Have some client-side behavior that updates the client-side display model according to the interaction.
This approach has a few significant benefits. First and foremost, all of my controller code is dirt simple and very clean. Second, the user never experiences page reloading. Instead, the browser-side view is updated with the results of the operation—much like a desktop application. Furthermore, because the client-side views and associated behavior are completely separate, it becomes very easy to test each part of the system in isolation. The has the effect of reducing my server-side code to something that gives back HTTP 200 or a JSON object containing input validation errors. In many regards, ASP.NET MVC becomes overkill. Even FubuMVC (an awesome project) is waaaay too much. I just need something to receive input, do a little validation and business verification before dispatching a message.
All of the above has been embodied into a small Javascript library I wrote (along with a corresponding server-side ASP.NET MVC counterpart) that I call Mvc2.TaskUI. I have been running this bit of Javascript and server-side C# code in production for several months now and it has been working great. The main part is actually the client-side Javascript behavior that I wrote as a 116-line jQuery plugin. It performs a kind of Ajax I call "Hijaxing". Interestingly enough, most of the code isn't in hijaxing the form submit and posting it to the server. Instead most of the code is related to error handling and displaying input errors.
Conclusion
This is definitely not a cure all. But it does solve a lot of pain…as long as your situation fits into the prescribed model.