asp.net-mvc - 具有自定义格式的 ASP.NET MVC ViewModel 映射

我正在处理的项目在域模型中有大量货币属性,我需要将它们格式化为 $#,###.##用于传输到 View 和从 View 传输。我对可以使用的不同方法有一个看法。一种方法是在 View 内显式格式化值,如 "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 :

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.

最佳答案

您是否考虑过使用扩展方法来格式化货币?

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


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

由于这显然是与 View 相关(而非模型相关)的问题,因此我会尽可能将其保留在 View 中。这基本上将它移动到十进制的扩展方法,但用法在 View 中。你也可以做一个 HtmlHelper 扩展:

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


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

如果你更喜欢这种风格。因为它是一个 HtmlHelper 扩展,所以它与 View 更相关。

https://stackoverflow.com/questions/1986960/

相关文章:

python - 使用 Python 在 OpenOffice/Microsoft Word 中格式

php - 定义跨平台money_format函数(Linux和Windows)

java - 在 String.format() 中选择参数

java - 如何在 Java 中将表格数据格式化为文本?

c# - 使 Console.WriteLine() 换行而不是字母

git - 在一系列 git 提交上运行 git-clang-format

ruby - 使用 %w 时包括空格

python - 是否有与 Linux/Unix "fold"命令等效的 Vim?

c# - 将电话号码解析为其部分

c++ - 使用 boost::date_time 库以毫秒为单位格式化时间