2009-12-31 85 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)