Monday, January 4, 2010

ASP.NET MVC ViewModel Value Formatting using AutoMapper

From ASP.NET MVC in Action section 4.4.1:

Views are difficult to unit test, so we want to keep them as thin as possible. … Notice in [the View Model] that all of the properties are strings. We’ll have the [properties] properly formatted before this view model object is placed in view data. This way, the view need not consider the object, and it can format the information properly.

To facilitate the formatting between the Domain Model and the View Model, a few of AutoMapper’s features may be utilized. Here’s a DomainModel containing a CurrencyProperty which will needed to formatted for human consumption:

public class DomainModel
    public decimal CurrencyProperty { get; set; }

Now, here is a ViewModel which will be used to transport the formatted value to the View:

public class ViewModel
    ///<summary>Currency Property - formatted as $#,###.##</summary>
    public string CurrencyProperty { get; set; }

Mapping from Domain Model to View Model

AutoMapper provides an easy way to create a mapping between two object types.  For particular tweaks for individual property mappings, the ForMember method can be used like:

///<summary>Setup mapping between domain and view model</summary>
static ViewModel()
    // map dm to vm
    Mapper.CreateMap<DomainModel, ViewModel>()
      .ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());

This sets up a mapping between the DomainModel and ViewModel and additionally applies a custom formatter for CurrencyProperty.  The formatter must implement the IValueFormatter interface like so:

public class CurrencyFormatter : IValueFormatter
    ///<summary>Formats source value as currency</summary>
    public string FormatValue(ResolutionContext context)
        return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);

…and a simple conversion constructor on the ViewModel:

/// <summary> Creates the view model from the domain model.</summary>
public ViewModel(DomainModel domainModel)
    Mapper.Map(domainModel, this);

Now, neither the Controller or View need concern about any formatting and can stay focused on orchestrating and layout:

public ViewResult Index()
    var model = new DomainModel{CurrencyProperty = 19.95m};

    var viewModel = new ViewModel(model);

    return View(viewModel);
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.TextBoxFor(m=>m.CurrencyProperty)%>
<%} %>

Aside: TextBoxFor is an upcoming ASP.NET MVC 2 feature that’s available today in MVC Futures or the RC.  Check out Matt’s post for some neat stuff.

Mapping from View Model back to Domain Model

So now the formatted value is being rendered – but how do we go about the reverse trip back to the server?  First, to define an action:

public ActionResult Index(ViewModel viewModel)
    var model = new DomainModel();

    // ... return a view or action result

Aside: for validating that what the user enters is in the correct format, see another post about jQuery Validate here.

… and a Map method on the ViewModel:

public void MapTo(DomainModel domainModel)
    Mapper.Map(this, domainModel);

But this mapping will fail without first creating a definition back from the ViewModel to the Model.  In the ViewModel’s static constructor:

  // from vm to dm
  Mapper.CreateMap<ViewModel, DomainModel>()
   .ForMember(dm => dm.CurrencyProperty, 
    mc => mc
     .FromMember(vm => vm.CurrencyProperty));

This utilizes another AutoMapper method - ResolveUsing - which can be used to get the string property back to a decimal.  The ValueResolver<TSource,TDestination> is defined like so:

public class CurrencyResolver : ValueResolver<string, decimal>
    ///<summary>Parses source value as currency</summary>
    protected override decimal ResolveCore(string source)
        return decimal.Parse(source, NumberStyles.Currency, CultureInfo.CurrentCulture);


There may be more elegant ways to accomplish formatting for MVC Views, but this method is quite workable.  In particular, I can imagine utilizing DataAnnotations’s DisplayFormatAttribute to decorate the Model or ViewModel and the framework automagically applying the formatting while rendering the View.

Wednesday, January 6, 2010 7:37:08 AM (Eastern Standard Time, UTC-05:00)
Clever stuff. Thanks.
Wednesday, January 6, 2010 9:28:37 AM (Eastern Standard Time, UTC-05:00)
Looks like it turned out pretty well. I still don't like the "everything's a string" approach to view models though, and I don't agree with that advice from "MVC In Action". I do agree with keeping as much logic out of the view as possible, but I think most formatting belongs either in the view or in an extension method if it's complex. The "string it all" approach also doesn't seem to be compatible with other trends that are emerging/have emerged in MVC, such as the opinionated input builders (http://www.lostechies.com/blogs/hex/archive/2009/06/09/opinionated-input-builders-for-asp-net-mvc-using-partials-part-i.aspx) where data type is important. True, you could achieve the same thing using attributes perhaps, but I'd rather just declare my members to have the correct type to begin with.
Thursday, January 7, 2010 8:45:08 PM (Eastern Standard Time, UTC-05:00)
@Matt - After looking over the Opinionated Input Builders, I agree that this it make for some nice easy model-based form views. It appears to have a mini view engine built in to assist in rendering the controls - it's pretty cool how it decides on which control to use with help from the type and attributes on the model.

The advantage to having strings properties in the ViewModel, as I see it, is that this is the only data type the web-client knows about. After all the complex rendering is done, the browser still gets a big string (structured into HTML) and likewise, communicates back to the server with strings. I definitely like the notion of abstracting it out more with MVC, but it feels like it'd be too much scaffolding to make it happen without another layer over the View engine (and Model binding as well), like they've done with the Input Builders project.
Comments are closed.