Wednesday, March 03, 2010

Maintainable MVC Series: View Model and Form Model

This article is part of the Maintainable MVC Series.



As mentioned in the previous post all views are strongly typed and every view has it's own View Model. Although this results in quite a lot of classes the upside is maintainability in that it's very straightforward. Every bit of data displayed in the view has a corresponding property in the View Model. As soon as it's no longer needed in the view we remove the property as well.



Of course, it sounds like it's in opposition with the DRY principle (Don't Repeat Yourself). However the data in the properties of the view models stems from the domain model, meaning the business rules are still in one place. To get the data from the domain model to the view models we have mapper classes in the presentation layer.




View model mapper



A simple example of how a controller action will call the mapper to convert a domain model into a view model can be seen below:



[csharp highlight="5"]
public ActionResult ShowItem(int id)
{
DomainModel item = repository.GetItem(id);

ViewModel viewModel = mapper.MapToViewModel(item);

return View("ShowItem", viewModel);
}
[/csharp]

Inside the MapToViewModel method we can have something like the following:



[csharp]
public ViewModel MapToViewModel(DomainModel domainModel)
{
ViewModel viewModel = new ViewModel
{
Id = domainModel.Id,
Name = domainModel.Name,
Description = domainModel.Description
};

return viewModel;
}
[/csharp]

But wait, writing this code is a dull job and it's easy to make a mistake. Therefore we make use of the AutoMapper library by Jimmy Bogard as much as possible.



AutoMapper



AutoMapper is a library with the purpose of mapping properties of a complex type to a simple type like a view model. It's not meant to be used the other way around, going from a poor model to a richer model with more properties.



Besides saving you of writing lots of code it also has test facilities to warn you if your target model (the view model) has new properties which have no counterpart in the source model (the domain model). An example of how we could use AutoMapper in the MapToViewModel method:



[csharp]
public ViewModel MapToViewModel(DomainModel domainModel)
{
Mapper.CreateMap<DomainModel, ViewModel>();

ViewModel viewModel = Mapper.Map<DomainModel, ViewModel>(domainModel);

return viewModel;
}
[/csharp]

If ViewModel gets richer and new properties are present in DomainModel no single line of code will have to be altered in the mapper.



For testing if all properties of ViewModel (that have a setter) can be mapped by AutoMapper we move the CreateMap line to a AutoMapperBootstrapper class:



[csharp]
public class AutoMapperBootstrapper
{
public void BootstrapAutoMapper()
{
Mapper.CreateMap<DomainModel, ViewModel>();
Mapper.CreateMap<DomainModel, OtherViewModel>();
}

public static void Bootstrap()
{
new AutoMapperBootstrapper().BootstrapAutoMapper();
}
}
[/csharp]

Having this code in a separate bootstrapper instead of the mapper classes is in order to have configuration centralized and because now testing of all these mappings is very easy. Just one test suffices for all mappings:



[csharp]
[Test]
public void CheckIfConfigurationIsValid()
{
AutoMapperBootstrapper.Bootstrap();

Mapper.AssertConfigurationIsValid();
}
[/csharp]

This test will fail as soon as there's a property in one of the target models that can't be mapped from the source model.



But what if our view model has to have a property that doesn't get it's value from the domain model? Well, we can chain some extra options to the CreateMap method to let AutoMapper ignore this property when mapping and when testing as well:



[csharp]
Mapper.CreateMap<DomainModel, ViewModel>()
.ForMember(x => x.NoDomainProperty, opt => opt.Ignore());
[/csharp]

Now we can set this property after mapping DomainModel to ViewModel:



[csharp highlight="4"]
public ViewModel MapToViewModel(DomainModel domainModel)
{
ViewModel viewModel = Mapper.Map<DomainModel, ViewModel>(domainModel);
viewModel.NoDomainProperty = repository.GetOtherData();

return viewModel;
}
[/csharp]

AutoMapper has a lot more in store like converting between types - for which you can even specify a custom converter - and other nice stuff. For more specifics visit the AutoMapper site.



Form Model



In going from the controller to the view we make use of view models. But what about going the other way? Do we post view models? No, to keep things clear we only post form models.



So are they different from view models? Not really. In code they are pretty much equal to view models, however it's more of a convention to name all models going outwards (towards the view) a view model and all models going inwards a form model. Also we try to make sure all properties can and will be filled by posted data. So no select lists and other presentation stuff in the form model.



In the last blog post I showed you how we pass the form model to the view containing the form. It is passed as a property of the view model for the specific view containing the form:



[csharp]
public class ViewModel {

public FormModel Data { get; set; }

public string NameLabel { get; set; }

public string DescriptionLabel { get; set; }

public string CountryLabel { get; set; }

public SelectList Countries { get; set; }

public string CountryChooseOption { get; set; }

}
[/csharp]

In the form view the data will be used like this:



[html]
<% using(Html.BeginForm()) {%>
<%= Html.AntiForgeryToken() %>
<label>
<%= Html.Encode(Model.NameLabel) %>:
<%= Html.TextArea("Name", Model.Data.Name) %>
</label>
<label>
<%= Html.Encode(Model.DescriptionLabel) %>:
<%= Html.TextArea("Description", Model.Data.Description) %>
</label>
<label>
<%= Html.Encode(Model.CountryLabel) %>:
<%= Html.DropDownList("Country", Model.Countries, Model.CountryChooseOption) %>
</label>
<input type="submit" value="Save">
<% } %>
[/html]

As you can see the values of the input fields are populated from the form model, while representation data like the dropdown list items and labels come from the viewmodel.



Posting the form model



A bonus of having all properties correspond to a form field is that all of them will be binded automatically by MVC. No need for custom binding here.



Usage of the form model will look like this:



[csharp hightlight="3,4,5,7"]
[AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult UpdateItem(int id, FormModel formModel)
{
DomainModel item = repository.GetItem(id);

item.Name = formModel.Name;
item.Description = formModel.Description;

repository.SaveItem(item);

return RedirectToAction("UpdateItem", new{ id });
}
[/csharp]

Of course the code above is just an example. The method will soon start to bloat if we add validation and other aspects (like setting success or error message to display on the page redirected to). So in order to keep the controller thin we'll move the highlighted lines into a separate form handler.



Form handler



The form handler handles updating the domain model from the form model. It does checks to make sure the item to be updated belongs to the user posting the data. Etc, etc. It's the beating heart if it comes to processing the commands coming in from the end user's browser.



Just like in the view model mapper there is at least one method for each form model to handle either creation of a new item or updating an existing one. Or handling other commands than the usual CRUD. It fits the CQRS (Command Query Responsibility Segregation) principle nicely. View models and mapping for query'ing, form models and form handlers for command handling. More on CQRS over here.



Validation



One last thing to talk about is validation. Validation is done on multiple levels. Our form models have their own validation, to make sure the posted data is what we want in terms of being required, being numeric or being an e-mail address. No difficult validation to see if shipping an order to a certain country is allowed. This belongs in the domain, because it's core business logic.



Another benefit of having validation on the form model is that client-side validation is done easily.



Handling the first level protection of validating the form model can be done in the controller or in the form handler. Additional levels of validation are fed back to the controller by the form handler. Probably in the form of a result object with a property indicating if handling was successful and a property with a list of failed validation and more properties if we want to be able to have a complex page flow in the site.



I'm still thinking of ways to have the (simple) validation rules of the domain model automatically being used for the form model as well. But for now we have some duplication over here, which - I know - is an issue regarding maintainability. But it isn't very often that a field being required one day stops being required the other day. Just simple validation over here. And the domain model validation is always there to back up the form model validation.

8 comments:

  1. very interesting series!!

    ReplyDelete
  2. I like this series, too. Keep up the good work!

    I am pretty close to where you are on validation, in that the duplication of validation rules is a big code smell for DRY. But now I'm thinking of it like the domain model and form model. There is overlap, sure, but the models serve different purposes and can be tailored to that purpose. Form model validation is pretty much just input validation, with some business rules overlaping. But more complex validation belongs further down in the stack in your domain layer.

    I used to think the ideal situation would be domain rules bubbling up to the UI layer so they can be validated there, but now I'm thinking this is just like having different models. It's OK to have overlap if it's a separate concern. Input validation and business rule validation are separate concerns to me.

    ReplyDelete
  3. Thanks for this series. The cross-section of a maintainable DRY, SOLID, MVC app is great. I look forward to the rest of the posts.

    ReplyDelete
  4. Thanks for sharing, I'm enjoying your series.

    I like your approach but I'm using strongly-typed input helpers (Html.TextBoxFor, etc.). It was giving me issues because it was generating input names like "Data_Name" instead of "Name" so the values weren't properly binding to the formModel in UpdateItem. The solution was to use the Bind attribute with the Prefix argument:

    e.g. public ActionResult UpdateItem(int id, [Bind(Prefix="Data")] FormModel formModel)

    Hope this helps anyone else who runs into the same issue.

    ReplyDelete
  5. @Mike - nice tip - thanks very much - I was wondering about that exact problem too...

    ReplyDelete
  6. Hi, excellent series, it has helped me a lot to start in the ASP MVC world
    I was wondering if you have examples about the form handlers, that part is a gray zone to me.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This applies not only during the IQ, OQ and PQ validation phases, but far earlier,
    beginning with the first meeting, and continuing through analysis of your process.
    Software validation is a part of the design validation for a finished device,
    but is not separately defined in the Quality System regulation.


    software validation

    ReplyDelete