我正在处理的项目在域模型中有大量货币属性,我需要对这些属性进行格式化,$#,###.##
以便在视图之间进行传输。我对可以使用的不同方法有一个看法。一种方法是在视图内显式格式化值,如Steve Michelotti 的“模式 1”:
...但这很快就开始违反DRY 原则。
首选方法似乎是在 DomainModel 和 ViewModel 之间的映射期间进行格式化(根据操作部分 4.4.1 和“模式 3”中的 ASP.NET MVC)。使用 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>()
...这样我就可以最大限度地减少每种对象类型的样板设置量。
我也有兴趣听到任何替代策略来完成自定义格式的往返视图。
起初我们可能想将这个简单的对象直接传递给视图,但是 DateTime? [模型中的] 属性会导致问题。例如,我们需要为它们选择一种格式,例如 ToShortDateString() 或 ToString()。当属性为空时,视图将被迫进行空检查以防止屏幕爆炸。视图很难进行单元测试,所以我们希望它们尽可能的精简。因为视图的输出是传递给响应流的字符串,所以我们将只使用对字符串友好的对象;也就是说,在对它们调用 ToString() 时永远不会失败的对象。ConferenceForm 视图模型对象就是一个例子。请注意,清单 4.14 中的所有属性都是字符串。在将此视图模型对象放入视图数据之前,我们将正确格式化日期。这样,视图不需要考虑对象,它可以正确地格式化信息。