我正在从事的项目在域模型中有大量货币属性,我需要将它们格式化为$#,###.##
用于向视图传输和从视图传输。我对可以使用的不同方法有一个想法。一种方法可能是在视图内显式设置值的格式,如下所示Steve Michelotti 的“模式 1” https://web.archive.org/web/20210124230412/http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx :
...但这开始违反干原则 https://en.wikipedia.org/wiki/Don%27t_repeat_yourself非常快。
首选方法似乎是在 DomainModel 和 ViewModel 之间的映射期间进行格式化(根据ASP.NET MVC 实际应用 https://www.manning.com/books/asp-net-mvc-in-action第 4.4.1 节和“模式3” https://web.archive.org/web/20210124230412/http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx)。使用 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);
}
}
Using IValueFormatter
这种方法效果很好。现在,如何将其从领域模型映射回视图模型?我尝试过使用自定义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>()
...这样我就可以最大限度地减少每种对象类型的样板设置数量。
我也有兴趣了解任何用于完成视图之间的自定义格式设置的替代策略。
From ASP.NET MVC 实际应用 https://www.manning.com/books/asp-net-mvc-in-action:
一开始我们可能会想通过
这个简单的对象直接
视图,但是日期时间?特性
[在模型中]会引起问题。
例如,我们需要选择一个
为它们格式化,例如
ToShortDateString() 或 ToString()。这
视图将被迫做空
检查以防止屏幕
当属性爆炸时
无效的。视图难以统一
测试,所以我们想让它们保持薄
尽可能。因为输出一个
view 是传递给的字符串
响应流,我们只会使用
字符串友好的对象;那
是,永远不会失败的对象
对它们调用 ToString()。这
ConferenceForm 视图模型对象是
这方面的例子。上市通知
4.14 所有属性都是字符串。我们会正确确定日期
在此视图模型之前格式化
对象放置在视图数据中。这
方式,视图不需要考虑
对象,并且它可以格式化
信息正确。