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
{
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:
static ViewModel()
{
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
{
public string FormatValue(ResolutionContext context)
{
return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
}
}
…and a simple conversion constructor on the ViewModel
:
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)%>
<%} %>
</asp:Content>
Aside: TextBoxFor is an upcoming 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:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(ViewModel viewModel)
{
var model = new DomainModel();
viewModel.MapTo(model);
}
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:
Mapper.CreateMap<ViewModel, DomainModel>()
.ForMember(dm => dm.CurrencyProperty,
mc => mc
.ResolveUsing<CurrencyResolver>()
.FromMember(vm => vm.CurrencyProperty));
This utilizes another AutoMapper method
0 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>
{
protected override decimal ResolveCore(string source)
{
return decimal.Parse(source, NumberStyles.Currency, CultureInfo.CurrentCulture);
}
}
Conclusions
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.