September 23, 2011

Better form handling in ASP.NET MVC

Yesterday I started work on a new ASP.NET MVC project built using common “best-practices” (programming against interfaces for looser coupling/testability, using dependency injection, creating view specific models etc.).

I think these practices will be familiar to many ASP.NET MVC developers, who would probably agree that they make our lives easier.

There are also many practices that we’ve “adopted”, often because that’s what we see in examples around the web. The purpose of my application (download the code here) is to identify and propose solutions to problems that are inherent in so many ASP.NET MVC applications (my own included).

Today I want to discuss how we handle forms in ASP.NET MVC. This post builds heavily on one of Jimmy Bogard’s articles with a few differences.

In most CRUD style ASP.NET MVC applications we typically have 3 types of controller actions

  1. Query (GET) actions
  2. Form (GET) actions
  3. Form (POST) actions

Query actions are pretty simple. We load our data from somewhere, build a view model and send it down to our view:

public ActionResult List() {
	var customers = customerService.GetCustomers();
	var model = new CustomerListModel(customers);
	
	return View(model);
}

Typically these presentational models will show a sub-section of information from your domain, so you will need to map the data you want from your domain objects to your view model. This can be achieved easily with AutoMapper (if you’re not into writing a load of repetitive left -> right mapping code).

Form (GET) actions could easily be classified as query actions as we are technically querying data and presenting it to the user. However, form view models tend to be considerably more complex. In addition to containing validation rules, they often have additional “buddy” data necessary to build the UI, a typical case being the data for a dropdown list.

Let’s look at an example:

[HttpGet]
public ActionResult Edit(Guid id)
{
	var customer = customerService.GetCustomerById(id);
	var countries = geoService.GetCountries();

	if (customer == null)
	{
		return RedirectToAction("list");
	}

	var model = new CustomerEditModel(customer, countries);
	return View(model);
}

The recommended approach for passing such “buddy” data is to create a property on your view model. As you can see from the above example, first we load our customer and list of countries (for the dropdown), then we pass it to our view model. Here our view model is performing the mapping, but as mentioned above you could easily use AutoMapper:

public CustomerEditModel(Customer customer, IEnumerable<Country> countries)
{
	Id = customer.Id;
	FirstName = customer.FirstName;
	LastName = customer.LastName;

	SetCountries(countries);
}

public void SetCountries(IEnumerable<Country> countries)
{
	Countries = new SelectList(countries.Select(s => s.Name), Country);
}

Then in our View we render our dropdown list like so:

<div class="editor-field">
	@Html.DropDownListFor(model => model.Country, Model.Countries)
</div>

Pretty standard stuff.

Now let’s get onto Form (POST) actions. I wonder how many times you have seen or written code like this:

[HttpPost]
public ActionResult Edit(CustomerEditModel model)
{
	if (!ModelState.IsValid)
	{
		model.SetCountries(geoService.GetCountries());
		return View(model);
	}
	
	var customer = customerService.GetCustomerById(model.Id);
	customer.FirstName = model.FirstName;
	customer.LastName = model.LastName;
	customer.Country = model.Country;

	customerService.UpdateCustomer(customer);

	return RedirectToAction("edit", new { id = model.Id });
}

First we check if ModelState is valid. If it’s not then we return the same view, making sure to repopulate all that “buddy” data that we need for our dropdown lists etc. Otherwise we can proceed with handling the posted form before redirecting somewhere. This works fine right so why change it?

Well first off all let’s look at a larger application that has 30 or so similar forms. We’re now writing the same code over and over:

  1. Check ModelState.IsValid
  2. If false, return an ActionResult.
  3. If true, handle form then return an ActionResult

I don’t like writing repetitive code. Before I get onto a better way of handling these steps, let’s first look at the issue of repopulating our form buddy data. This is incredibly annoying especially as you’ll have to do it 4 times for a typical Create/Edit scenario (on both GET and POST).

My solution to this is to create view model “Enrichers”. As the name suggests, these “Enrich” view models, either by adding additional information or performing processing of some sort. These are designed specifically for post-processing of a populated model.

All Enrichers implement IViewModelEnricher<TViewModel>:

public interface IViewModelEnricher<TViewModel>
{
    void Enrich(TViewModel viewModel);
}

Let’s look at an implementation of this, our CustomerEditModelEnricher:

public class CustomerEditModelEnricher : IViewModelEnricher<CustomerEditModel>
{
    private readonly IGeoService geoService;

    public CustomerEditModelEnricher(IGeoService geoService) {
        this.geoService = geoService;
    }
    
    public void Enrich(CustomerEditModel viewModel) {
        if (viewModel == null)
            return;

        var countries = new SelectList(geoService.GetCountries().Select(c => c.Name), viewModel.Country);
        viewModel.Countries = countries;
    }
}

Here we “Enrich” our CustomerEditModel with a SelectList of Countries obtained from our GeoService. Our constructor dependencies will be populated automatically using our IoC container.

Now we need a way to return an “Enriched” view result. We do that with a custom View result named (you guessed it) “EnrichedViewResult”:

public class EnrichedViewResult<T> : ViewResult
{
    public EnrichedViewResult(string viewName, ViewDataDictionary viewData)
    {
        this.ViewName = viewName;
        this.ViewData = viewData;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (this.Model != null)
        {
            var enricher = DependencyResolver.Current.GetService<IViewModelEnricher<T>>();
            if (enricher != null)
            {
                enricher.Enrich((T)this.Model);
            }
        }

        base.ExecuteResult(context);
    }
}

When our custom result is executed we ask our IoC container (via DependencyResolver) for an enricher of the given type and run our Model through it.

So how do we locate the correct enricher? That’s where the power of StructureMap comes in:

x.Scan(scan =>
		{
			scan.TheCallingAssembly();
			scan.WithDefaultConventions();
			scan.ConnectImplementationsToTypesClosing(typeof(IViewModelEnricher<>));
		});

The method “ConnectImplementationsToTypesClosing(type)” does exactly what it says on the tin. It looks for any implementations of IViewModelEnricher and registers them.

Finally we have some helper methods on our base controller for returning an EnrichedViewResult:

public EnrichedViewResult<T> EnrichedView<T>(T model) {
	return EnrichedView(null, model);
}

public EnrichedViewResult<T> EnrichedView<T>(string viewName, T model){
	if (model != null) {
		ViewData.Model = model;
	}
	return new EnrichedViewResult<T>(viewName, ViewData);
}

This removes a lot of repitive code from our controller and means that the population of such buddy data is done in one place:

[HttpGet]
public ActionResult Edit(Guid id)
{
	var customer = customerService.GetCustomerById(id);

	if (customer == null)
	{
		return RedirectToAction("list");
	}

	var model = new CustomerEditModel(customer);
	return EnrichedView(model);
}

[HttpPost]
[UnitOfWork]
public ActionResult Edit(CustomerEditModel model)
{
	if (!ModelState.IsValid)
	{
		return EnrichedView(model);
	}
	
	var customer = customerService.GetCustomerById(model.Id);
	customer.FirstName = model.FirstName;
	customer.LastName = model.LastName;
	customer.Country = model.Country;

	customerService.UpdateCustomer(customer);

	return RedirectToAction("edit", new { id = model.Id });
}

With that issue out the way, lets look at cleaning up the handling of our POST. First I would recommend reading through Jimmy’s article as this is what I was using originally. Now let me show you my version:

public class FormActionResult<T> : ActionResult
{
    private readonly T form;
    public Action<T> Handler { get; set; }
    public Func<T, ActionResult> SuccessResult;
    public Func<T, ActionResult> FailureResult;

    public FormActionResult(T form) {
        this.form = form;
    }
    
    public FormActionResult(T form, Action<T> handler, Func<T, ActionResult> successResult, 
        Func<T, ActionResult> failureResult)
    {
        this.form = form;
        Handler = handler;
        SuccessResult = successResult;
        FailureResult = failureResult;
    }
    
    public override void ExecuteResult(ControllerContext context)
    {
        var viewData = context.Controller.ViewData;

        if (!viewData.ModelState.IsValid) // 1 Check ModelState
        {
            FailureResult(form).ExecuteResult(context);
        }
        else
        {
            // 2 execute handler
            Handler(form);

            // 3 return success result
            SuccessResult(form).ExecuteResult(context);
        }
    }
}

Looking at the above action result you can see that it covers the three steps previously mentioned. With a few extension methods (check the source) we are able to express our intentions clearly. Our Edit POST method now becomes:

[HttpPost]
[UnitOfWork]
public ActionResult Edit(CustomerEditModel model) {
	return Handle(model)
		.With(form =>
		{
			var customer = customerService.GetCustomerById(form.Id);

			if (customer == null)
				return;

			customer.FirstName = form.FirstName;
			customer.LastName = form.LastName;
			customer.Country = form.Country;

			customerService.UpdateCustomer(customer);
		})
		.OnSuccess(form => RedirectToAction("edit", new { id = form.Id }));
}

The “Handle” method on our base controller sets a default for the failure result (returns an enriched view), otherwise you can just call “OnFailure”. The full flow can be seen below:

return Handle(model)
	.With(form =>
	{
		// Do something
	})
	.OnSuccess(form => RedirectToAction("edit", new { id = form.Id }))
	.OnFailure(form => RedirectToAction("failed");

There are a few differences here from Jimmy’s version. The first is that I’m making the form available to the Success and Failure delegates. This is something that we found we needed, normally in order to provide the results with context specific route values.

Another big difference is that we are declaring our handlers inline. This is really a personal preference thing and I may even add support for both. I found that using separate handlers was a bit of an overkill, especially as we intend to follow a more command based approach in our architecture. A separate handler therefore just to call Bus.Send(command) was a bit unecessary. We also had a few controllers where we were using both methods so we were regularly switching between controllers and handlers. The advice then is probably to choose the method that works for you and stick to it :)

Finally I chose to use a fluent interface to build the result. I found that this expressed our intentions much more clearly. It’s just a small thing but I think it improves the readability of the code.

It’s a shame that such things aren’t built into the framework as this is such a common pattern. The next task is how to handle complex business validation.

In one application (using Jimmy’s code) we changed our handlers to accept a Validation dictionary which allowed us to pass ModelState to the handler. We could then do complex business validation (that maybe needed to touch the database) and then add errors if we needed to, which would result in the FailureResult being executed.

However, after re-evaluating this process and reading more into CQRS and command based systems I really see this as a separate responsibility. If we are to process a command (either directly or on a bus) we need to make sure it is valid before it is processed.

I intend to use a validation library like Fluent Validation that can easily be hooked into our form processing pipeline. This way we can check input validation, then check business validation before deciding what to do. That’s a post for another day so for now, feel free to comment below or fork my code and make it better :)

© 2022 Ben Foster