……但这很快就违反了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?我尝试过使用自定义类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映射的属性,因为属性具有相同的名称 – 是否有更好的方法来定义此映射?正如我所提到的,有许多具有货币值的属性需要以这种方式映射.
话虽如此 – 有没有办法通过全局定义一些规则来自动解决这些映射? 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中完成自定义格式化的替代策略.
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.
@R_403_323@
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 } }