具有自定义格式的ASP.NET MVC ViewModel映射

时间:2009-12-31 20:20:07

标签: asp.net-mvc formatting automapper

我正在处理的项目在域模型中有大量的货币属性,我需要将它们格式化为$#,###.##,以便与视图进行传输。我已经对可以使用的不同方法有了一些看法。一种方法可以是在视图中明确地格式化值,如"Pattern 1" from Steve Michelotti

  

...但这很快就会违反DRY principle

首选方法似乎是在DomainModel和ViewModel之间进行映射时进行格式化(根据ASP.NET MVC in Action第4.4.1节和"Pattern 3")。使用AutoMapper,这将产生如下代码:

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

使用IValueFormatter这种方式效果很好。现在,如何将它从DomainModel映射回ViewModel?我尝试过使用自定义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);
 }
}

然后将其映射到:

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

哪个会满足这个测试:

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

...但我觉得在执行FromMember之后我不需要明确定义它与ResolveUsing映射的属性,因为属性具有相同的名称 - 是否存在定义此映射的更好方法是什么?正如我所提到的,有许多具有货币值的属性需要以这种方式进行映射。

话虽如此 - 有没有办法通过全局定义一些规则来自动解决这些映射? ViewModel属性已经使用DataAnnotation属性[DataType(DataType.Currency)]进行了修饰,因此我希望我可以定义一些规则:

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

...这样我就可以最小化每种对象类型的样板设置量。

我也有兴趣了解在View中完成自定义格式设置的其他策略。


来自ASP.NET MVC in Action

  

起初我们可能会试图通过   这个简单的对象直接到了   查看,但DateTime?性能   [在模型中]将导致问题。   例如,我们需要选择一个   格式化他们,如   ToShortDateString()或ToString()。该   视图将被强制为null   检查以保持屏幕   财产爆炸时   空值。观点很难单位   测试,所以我们希望保持它们的薄   尽可能。因为输出了   view是传递给的一个字符串   响应流,我们只会使用   对象友好的对象;那   是,对象永远不会失败   在它们上调用ToString()。该   ConferenceForm视图模型对象是一个   这样的例子。在列表中注意   4.14所有属性都是字符串。我们会正确地说明日期   在此视图模型之前格式化   对象放在视图数据中。这个   方式,视图不需要考虑   对象,它可以格式化   信息正确。

3 个答案:

答案 0 :(得分:6)

您是否考虑过使用扩展方法格式化资金?

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


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

由于这显然是与视图相关的(与模型无关的)问题,如果可能的话,我会尝试将其保留在视图中。这基本上将它移动到十进制的扩展方法,但用法在视图中。您还可以执行HtmlHelper扩展:

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


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

如果你更喜欢这种风格。它更像View,因为它是一个HtmlHelper扩展。

答案 1 :(得分:3)

您是否考虑过将ViewFormat放在ViewModel上?这就是我使用的,它快速而简单。

ViewModel :
    [DisplayFormat(DataFormatString = "{0:c}", ApplyFormatInEditMode = true)]
    public decimal CurrencyProperty { get; set; }


View :
    @Html.DisplayFor(m => m.CurrencyProperty)

答案 2 :(得分:2)

您正在寻找自定义TypeConverter:

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

然后创建转换器:

public class MoneyToDecimalConverter : TypeConverter<string, decimal>
{
   protected override decimal ConvertCore(string source)
   {
      // magic here to convert from string to decimal
   }
}