June 20, 2012

If Then, If Then, If Then, MVC

I’m sure many ASP.NET MVC developers are used to writing code like this:

[HttpGet]
public ActionResult List() 
{
	var list = new[] { "John", "Pete", "Ben" };
	
	if (Request.AcceptTypes.Contains("application/json")) {
		return Json(list, JsonRequestBehavior.AllowGet);
	}

	if (Request.IsAjaxRequest()) [
		return PartialView("_List", list);
	}
	
	return View(list);
}

[HttpPost]
public ActionResult Index(SomeModel model) 
{
	if (!ModelState.IsValid) {
		if (Request.IsAjaxRequest()) {
			return Json(new { error = "Some Error" });
		}
		return View(model);
	}
	
	TempData["message"] = "Hello " + model.Name;
	
	if (Request.IsAjaxRequest()) {
		return Javascript("window.location = '" 
			+ Url.Action("index") + "';");
	}
	
	return RedirectToAction("index");
}

In the GET action:

  • If the request accept headers contain “application/json” we return JSON
  • If it’s an ajax request we return a partial view
  • Otherwise, we return a view

In the POST action:

  • We validate model state
  • If the model state is invalid and the request is:
    • AJAX - we return a pretty useless error
    • Normal - we return the View
  • If the model state is valid and the request is:
    • AJAX - we return a Javascript result to perform a redirect
    • Normal - we perform a normal redirect

At first glance this code may not look too bad. But start writing this same code on 10+ actions and you’ll start to uncomfortable - if you don’t, you should do.

Almost all of the If, Then, behaviour can be encapsulated into custom Action Filters or View Results.

Later this week I’ll be releasing Fabrik.Common.Web and Fabrik.Common.MvcBoilerPlate; two projects that help to get around some of the inadequacies of ASP.NET MVC.

As a preview, both of the above actions can now be rewritten as:

[HttpGet]
[AjaxView(usePartialViewPrefix: true)]
public ActionResult List()
{
	var list = new[] { "John", "Pete", "Ben" };
	return View(list);
}

[HttpPost]
[ValidateModelState]
[AjaxRedirect]
public ActionResult Index(SomeModel model)
{           
	TempData["message"] = "Hello " + model.Name;
	return Redirect(Url.Action("index"));           
}

For the GET method we have a global action filter that checks if the client is requesting JSON.

I can make a normal HTTP request to “/list” and the full View will be displayed. Or, on the client I can make an AJAX request like so:

$('#list').load('Url.Action("list")');

And the partial view “_List” will be returned.

Of course, if I want to just get the JSON instead, I just change my client side code to the following:

$.ajax({
    url: '@Url.Action("list")',
    dataType: 'json',
    success: function (data) {
        $('#list').append('<ul>');
        $.each(data, function (i, name) {
            $("#list ul").append('<li>' + name + '</li>');
        });
    }
});

For the POST method, model state is validated automatically. If it’s an AJAX request we actually send the model state errors back to the client and hook into the jQuery validator plugin. The redirection is also AJAX aware.

One goal of the project will be to gracefully upgrade an MVC application to use AJAX without requiring any change on the server.

Stay tuned.

© 2022 Ben Foster