2009-12-31 14 views
6

Il progetto su cui sto lavorando ha un gran numero di proprietà di valuta nel modello di dominio e ho bisogno di formattarli come $#,###.## per la trasmissione da e verso la vista. Ho avuto una visione di opinioni su diversi approcci che potrebbero essere utilizzati. Un approccio potrebbe essere quello di formattare i valori esplicitamente all'interno della vista, come in "Pattern 1" from Steve Michelotti:ASP.NET MVC ViewModel mapping con formattazione personalizzata

... ma questo inizia a violare DRY principle molto rapidamente.

L'approccio preferito sembra essere quello di eseguire la formattazione durante la mappatura tra DomainModel e un ViewModel (come da ASP.NET MVC in Action sezione 4.4.1 e "Pattern 3"). Utilizzando automapper, questo si tradurrà in un certo codice come il seguente:

[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); 
} 
} 

Utilizzando IValueFormatter questo modo funziona alla grande. Ora, come mapparlo da DomainModel a ViewModel? Ho provato con un costume 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); 
} 
} 

E quindi mappati con:

// from vm to dm 
    Mapper.CreateMap<ViewModel, DomainModel>() 
    .ForMember(dm => dm.CurrencyProperty, 
    mc => mc 
    .ResolveUsing<CurrencyResolver>() 
    .FromMember(vm => vm.CurrencyProperty)); 

in grado di soddisfare questa prova:

///<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)); 
} 

... Ma io sento che Non dovrei aver bisogno di definire esplicitamente la proprietà con cui viene mappato da FromMember dopo aver fatto ResolveUsing poiché le proprietà hanno lo stesso nome - c'è un migliore modo per definire questa mappatura? Come ho già detto, ci sono un buon numero di proprietà con valori di valuta che dovranno essere mappati in questo modo.

Detto questo: esiste un modo per risolvere automaticamente questi mapping definendo alcune regole a livello globale? Le proprietà ViewModel sono già decorate con DataAnnotation attributi [DataType(DataType.Currency)] per la convalida, quindi speravo che potrei definire una regola che fa:

if (destinationProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency)) 
    then Mapper.Use<CurrencyFormatter>() 
if (sourceProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency)) 
    then Mapper.Use<CurrencyResolver>() 

... in modo che possa ridurre al minimo la quantità di configurazione boilerplate per ciascuna delle tipi di oggetti.

Sono anche interessato a conoscere eventuali strategie alternative per eseguire la formattazione personalizzata da-e-dalla vista.


Da ASP.NET MVC in Action:

In un primo momento ci potrebbe essere tentati di passare questo semplice oggetto dritto alla vista , ma il DateTime? le proprietà [nel Modello] causeranno problemi. Per esempio, dobbiamo scegliere una formattazione per loro come ToShortDateString() o ToString(). La vista sarebbe forzata a fare il controllo nullo per mantenere lo schermo da saltando su quando le proprietà sono null. Le viste sono difficili da testare con l'unità , quindi vogliamo tenerle sottili come possibile.Poiché l'output di una vista è una stringa passata al flusso di risposta , utilizzeremo solo gli oggetti che sono compatibili con le stringhe; che è, oggetti che non falliranno mai quando ToString() viene richiamato su di essi. L'oggetto del modello di visualizzazione ConferenceForm è un esempio di . Si noti nella lista 4.14 che tutte le proprietà sono stringhe. Avremo le date correttamente formattate prima che l'oggetto modello vista venga inserito nei dati di visualizzazione. Questo modo , la vista non deve prendere in considerazione l'oggetto e può formattare correttamente le informazioni .

+0

<% = string.Format ("{0: c}", Model.CurrencyProperty)%> sembra piuttosto a me. Forse mi ci sono abituato ... –

risposta

2

Un TypeConverter personalizzato è quello che stai cercando:

Mapper.CreateMap<string, decimal>().ConvertUsing<MoneyToDecimalConverter>(); 

quindi creare il convertitore:

public class MoneyToDecimalConverter : TypeConverter<string, decimal> 
{ 
    protected override decimal ConvertCore(string source) 
    { 
     // magic here to convert from string to decimal 
    } 
} 
+0

Grazie per la risposta Jimmy. Ho cercato di usare TypeConverter , ma il problema che ho riscontrato nel mio caso è che verrà applicato a * tutti * i mapping da decimale a stringa. Sfortunatamente, solo alcune delle proprietà decimali sono valuta. Ho pensato di creare un wrapper attorno ai decimali - (class CurrencyDecimal: Decimal), ma poi ho potuto facilmente aggiungere operazioni di cast implicite tra il tipo e la stringa. Quello che mi piacerebbe davvero avere è qualcosa come TypeConverter in grado di esaminare gli attributi delle proprietà - forse vedrò di scriverlo a volte se non esiste. –

6

Hai mai pensato di utilizzare un metodo di estensione per formattare il denaro?

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


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

Dal momento che questo è chiaramente un (non modello di connessi) problema vista legati, mi piacerebbe provare a tenerlo in vista, se possibile. Questo in pratica lo sposta su un metodo di estensione su decimale, ma l'utilizzo è nella vista. Si potrebbe anche fare un'estensione HtmlHelper:

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


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

Se ti è piaciuto quello stile meglio. È un po 'più correlato alla vista poiché è un'estensione HtmlHelper.

+0

Sì, quelli sicuramente hanno più senso di fare lo string.Format() ogni volta nella Vista. Il problema che sto affrontando è che il ViewModel sarà spesso reso al client per il consumo di javascript - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- to-JavaScript-in-ASPNET-MVC.aspx o durante le richieste AJAX. In questi casi, avrei bisogno di fare la formattazione sul livello client, che è meno che desiderabile - in effetti, sento che vorrei fare un sacco di sforzi extra solo per avere tutte le preoccupazioni di formattazione/analisi separate in un livello . –

+1

E 'anche fastidioso per me che MVC abbia un meccanismo robusto per analizzare le richieste in arrivo tramite l'associazione Modello personalizzata, ma non fornisce lo stesso tipo di esperienza per la formattazione durante il rendering View. –

+0

Non ho un problema con la vista o il client è quello che prende le decisioni di formattazione. In genere preferisco che il controller o il modello scelgano come rappresentare i dati - che sembra violare il principio della separazione delle preoccupazioni. Che cosa succede se i diversi client/viste (mobile vs web, ad esempio) vogliono renderlo in modi diversi? – tvanfosson

3

Avete considerato mettere un DisplayFormat sul tuo ViewModel? Questo è quello che uso ed è veloce e semplice.

ViewModel : 
    [DisplayFormat(DataFormatString = "{0:c}", ApplyFormatInEditMode = true)] 
    public decimal CurrencyProperty { get; set; } 


View : 
    @Html.DisplayFor(m => m.CurrencyProperty)