2009-12-31 80 views
6

我正在处理的项目在域模型中有大量的货币属性,我需要将这些格式设置为$#,###.##来传输视图。我已经对可以使用的不同方法有了一些看法。一种方法可以是明确的值设置格式的视图中,在"Pattern 1" from Steve Michelotti使用自定义格式的ASP.NET MVC ViewModel映射

...但是这违反开始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)); 
} 

...但我觉得我不应该在做ResolveUsing之后明确定义它正在映射的属性FromMember,因为属性具有相同的名称 - 是否有更好的如何定义这个映射?正如我所提到的,有很多货币值需要以这种方式映射。

这就是说 - 我有办法通过定义全局的规则来自动解决这些映射吗?该视图模型属性已经装饰了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>() 

...所以,我可以最大限度地减少为每个样板安装量对象类型。

我也有兴趣听到任何实现自定义格式的视图的替代策略。


ASP.NET MVC in Action

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

+0

<(%)=的String.Format( “{0:C}”,Model.CurrencyProperty)%>看起来相当我。也许我只是习惯了它...... – 2010-01-01 15:23:12

回答

2

自定义类型转换器是你在找什么:

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 
    } 
} 
+0

感谢您的回复吉米。我已经看过使用TypeConverter ,但我发现在我的案例中的问题是,它将应用于从小数到字符串的* all *映射。不幸的是,只有一些小数属性是货币。我想可能是围绕十进制做一个包装 - (CurrencyDecimal:Decimal),但是我可以轻松地在类型和字符串之间添加隐式转换操作。我真正想要的是类似于TypeConverter的东西,它可以检查属性的属性 - 也许我会在不存在的时候编写它。 – 2010-01-08 02:00:48

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) %> 

如果你喜欢那种风格更好。由于它是一个HtmlHelper扩展,所以它更像View。

+0

是的,那些绝对比每次在视图中执行string.Format()更有意义。我面临的问题是ViewModel通常会呈现给客户端以供JavaScript使用 - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- to-JavaScript-in-ASPNET-MVC.aspx或AJAX请求期间。在这些情况下,我需要在客户端图层上进行格式设置,这是不太理想的 - 事实上,我觉得我会尽一切努力让所有的格式化/解析问题在一层中分开。 – 2009-12-31 21:04:25

+1

我也觉得MVC有一个强大的机制来通过自定义模型绑定来解析传入的请求,但是在视图渲染过程中没有提供相同类型的格式化体验。 – 2009-12-31 21:07:56

+0

我没有视图或客户端做出格式化决定的问题。一般来说,我更喜欢控制器或模型选择如何表示数据 - 这似乎违反了关注点分离原则。如果不同的客户端/视图(例如手机与网页)想要以不同的方式呈现它? – tvanfosson 2009-12-31 21:39:16

3

你有没有考虑把一个DisplayFormat你的视图模型?这是我使用的,它很快捷,简单。

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


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