views:

612

answers:

2

The project I'm working on has a large number of currency properties in the domain model and I'm needing for format these as $#,###.## for transmitting to and from the view. I've had a view thoughts as to different approaches which could be used. One approach could be to format the values explicitly inside the view, as in "Pattern 1" from Steve Michelotti :

<%= string.Format("{0:c}", Model.CurrencyProperty) %>

...but this starts violating DRY principle very quickly.

The preferred approach appears to be to do the formatting during the mapping between DomainModel and a ViewModel (as per ASP.NET MVC in Action section 4.4.1 and "Pattern 3"). Using AutoMapper, this will result in some code like the following:

[TestFixture]
public class ViewModelTests
{
 [Test]
 public void DomainModelMapsToViewModel()
 {
  var domainModel = new DomainModel {CurrencyProperty = 19.95m};

  var viewModel = new ViewModel(domainModel);

  Assert.That(viewModel.CurrencyProperty, Is.EqualTo("$19.95"));
 }
}

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

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

 ///<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>());
 }

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

 public ViewModel() { }
}

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);
 }
}

Using IValueFormatter this way works great. Now, how to map it back from the DomainModel to ViewModel? I've tried using a custom class CurrencyResolver : ValueResolver<string,decimal>

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);
 }
}

And then mapped it with:

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

Which will satisfy this test:

 ///<summary>DomainModel maps to ViewModel</summary>
 [Test]
 public void ViewModelMapsToDomainModel()
 {
  var viewModel = new ViewModel {CurrencyProperty = "$19.95"};

  var domainModel = new DomainModel();

  Mapper.Map(viewModel, domainModel);

  Assert.That(domainModel.CurrencyProperty, Is.EqualTo(19.95m));
 }

... But I'm feeling that I shouldn't need to explicitly define which property it is being mapped from with FromMember after doing ResolveUsing since the properties have the same name - is there a better way to define this mapping? As I mentioned, there are a good number of properties with currency values that will need to be mapped in this fashion.

That being said - is there a way I could have these mappings automatically resolved by defining some rule globally? The ViewModel properties are already decorated with DataAnnotation attributes [DataType(DataType.Currency)] for validation, so I was hoping that I could define some rule that does:

if (destinationProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency)) 
  then Mapper.Use<CurrencyFormatter>()
if (sourceProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency)) 
  then Mapper.Use<CurrencyResolver>()

... so that I can minimize the amount of boilerplate setup for each of the object types.

I'm also interested in hearing of any alternate strategies for accomplishing custom formatting to-and-from the View.


From ASP.NET MVC in Action:

At first we might be tempted to pass this simple object straight to the view, but the DateTime? properties [in the Model] will cause problems. For instance, we need to choose a formatting for them such as ToShortDateString() or ToString(). The view would be forced to do null checking to keep the screen from blowing up when the properties are null. Views are difficult to unit test, so we want to keep them as thin as possible. Because the output of a view is a string passed to the response stream, we’ll only use objects that are stringfriendly; that is, objects that will never fail when ToString() is called on them. The ConferenceForm view model object is an example of this. Notice in listing 4.14 that all of the properties are strings. We’ll have the dates 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.

A: 

Have you considered using an extension method to format money?

public static string ToMoney( this decimal source )
{
    return string.Format( "{0:c}", source );
}


<%= Model.CurrencyProperty.ToMoney() %>

Since this is clearly a view-related (not model-related) issue, I'd try to keep it in the view if at all possible. This basically moves it to an extension method on decimal, but the usage is in the view. You could also do an HtmlHelper extension:

public static string FormatMoney( this HtmlHelper helper, decimal amount )
{
    return string.Format( "{0:c}", amount );
}


<%= Html.FormatMoney( Model.CurrencyProperty ) %>

If you liked that style better. It is somewhat more View-related as it's an HtmlHelper extension.

tvanfosson
Yes, those definitely make more sense than doing the string.Format() every time in the View. The issue I'm facing is that the ViewModel will often be rendered to the client for javascript consumption - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model-to-JavaScript-in-ASPNET-MVC.aspx or during AJAX requests. In these cases, I'd need to do the formatting on the client layer, which is less than desirable - in fact, I feel I would go though a bunch of extra effort just to have all the formatting/parsing concerns separated in one layer.
James Kolpack
It is also irksome to me that that MVC has a robust mechanism for parsing incoming requests via custom Model binding, but doesn't provide the same kind of experience for formatting during View rendering.
James Kolpack
I don't have a problem with the view or the client being the one to make the formatting decisions. Generally I prefer that over the controller or model choosing how to represent the data -- that seems to violate the separation of concerns principle. What if different clients/views (mobile vs. web, for instance) want to render it in different ways?
tvanfosson
+1  A: 

A custom TypeConverter is what you're looking for:

Mapper.CreateMap<string, decimal>().ConvertUsing<MoneyToDecimalConverter>();

Then create the converter:

public class MoneyToDecimalConverter : TypeConverter<string, decimal>
{
   protected override decimal ConvertCore(string source)
   {
      // magic here to convert from string to decimal
   }
}
Jimmy Bogard
Thanks for the reply Jimmy. I've looked at using TypeConverter<TSource,TDest>, but the issue I found in my case is that it will get applied to *all* mappings from decimal to string. Unfortunately, only some of the decimal properties are currency. I thought of perhaps making a wrapper around decimal - (class CurrencyDecimal : Decimal), but then I could just as easily add implicit cast operations between the type and string. What I'd really like to have is a something like TypeConverter that can examine property attributes - perhaps I'll look at writing this sometime if it doesn't exist.
James Kolpack