2009-12-31 9 views
6

Das Projekt, an dem ich arbeite, hat eine große Anzahl von Währungseigenschaften im Domänenmodell und ich brauche für das Format diese als $#,###.## für die Übertragung von und von der Ansicht. Ich habe mir Gedanken zu verschiedenen Ansätzen gemacht, die verwendet werden könnten. Ein Ansatz könnte sein, die Werte zu formatieren explizit in der Ansicht, wie in "Pattern 1" from Steve Michelotti:ASP.NET MVC ViewModel Mapping mit benutzerdefinierter Formatierung

... aber das beginnt DRY principle sehr schnell verletzen.

Die bevorzugte Vorgehensweise scheint die Formatierung während der Zuordnung zwischen DomainModel und einem ViewModel zu sein (gemäß ASP.NET MVC in Action Abschnitt 4.4.1 und "Pattern 3"). Mit AutoMapper, wird dies wie folgt aus in einigen Code zur Folge:

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

Mit IValueFormatter diese Art und Weise funktioniert super. Wie wird es vom DomainModel zu ViewModel zurückverwandelt? Ich habe versucht, 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); 
} 
} 

eine benutzerdefinierte und anschließend kartiert es mit:

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

, die diesen Test wird erfüllen:

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

... Aber ich fühle, dass Ich sollte nicht explizit definieren müssen, aus welcher Eigenschaft es mit FromMember nach ResolveUsing gemappt wird, da die Eigenschaften den gleichen Namen haben - gibt es eine bessere Möglichkeit, diese Zuordnung zu definieren? Wie ich bereits erwähnt habe, gibt es eine gute Anzahl von Eigenschaften mit Währungswerten, die auf diese Weise abgebildet werden müssen.

Das Gesagte - gibt es eine Möglichkeit, diese Mappings automatisch zu lösen, indem Sie eine Regel global definieren? Die Ansichtsmodell Eigenschaften sind bereits dekoriert mit DataAnnotation Attribute [DataType(DataType.Currency)] für die Validierung, so dass ich hatte gehofft, dass ich eine Regel definieren könnte, das tut:

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

... so dass ich die Menge an vorformulierten Setup für jede der minimieren Objekttypen.

Ich bin auch daran interessiert zu hören, alternative Strategien für die benutzerdefinierte Formatierung zu und von der Ansicht zu erreichen.


Von ASP.NET MVC in Action:

Zunächst könnten wir dieses einfache Objekt direkt in die Ansicht, aber die Datetime zu passieren versucht sein? Eigenschaften [im Modell] wird Probleme verursachen. Zum Beispiel müssen wir eine Formatierung für sie wie ToShortDateString() oder ToString() wählen. Die Ansicht wäre gezwungen, Null zu machen Prüfung, um den Bildschirm von in die Luft zu sprengen, wenn die Eigenschaften null sind. Ansichten sind schwierig zu Einheit Test, so wollen wir sie so dünn wie möglich halten .Da die Ausgabe einer Ansicht eine Zeichenfolge ist, die an den Antwortstream übergeben wird, verwenden wir nur Objekte, die stringfreundlich sind. dass ist, Objekte, die niemals fehlschlagen werden, wenn ToString() für sie aufgerufen wird. Das ConferenceForm-Ansichtsmodellobjekt ist ein Beispiel dafür. Beachten Sie in Listing 4.14, dass alle Eigenschaften Strings sind. Wir haben die Daten richtig formatiert, bevor diese Ansicht Modell Objekt in Ansicht Daten platziert wird. Diese Weise, die Ansicht muss nicht das Objekt betrachten, und es kann die Information richtig formatieren.

+0

<% = string.Format ("{0: c}", Model.CurrencyProperty)%> sieht gut aus für mich. Vielleicht bin ich einfach daran gewöhnt ... –

Antwort

2

Ein benutzerdefinierter Typeconverter ist das, was Sie suchen:

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

dann den Konverter erstellen:

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

Danke für die Antwort Jimmy. Ich habe mit TypeConverter betrachtet, aber das Problem, das ich in meinem Fall gefunden habe, ist, dass es auf * alle * Zuordnungen von dezimal zu string angewendet wird. Leider sind nur einige der Dezimaleigenschaften Währung. Ich dachte daran, vielleicht einen Wrapper um Dezimal - (Klasse CurrencyDecimal: Decimal) zu machen, aber dann könnte ich genauso einfach implizite Cast-Operationen zwischen dem Typ und der Zeichenfolge hinzufügen. Was ich wirklich gerne haben möchte, ist etwas wie TypeConverter, das Eigenschaftenattribute untersuchen kann - vielleicht werde ich mir das mal ansehen, wenn es nicht existiert. –

6

Haben Sie überlegt, eine Erweiterungsmethode zu verwenden, um Geld zu formatieren?

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


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

Da dies eindeutig ein Ansicht bezogene (nicht modellbezogen) Problem, würde ich versuchen, es im Auge zu behalten, wenn überhaupt möglich. Dies verschiebt es im Grunde zu einer Erweiterungsmethode für dezimal, aber die Verwendung ist in der Ansicht. Sie könnten auch eine HtmlHelper-Erweiterung:

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


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

Wenn Sie diesen Stil besser mochten. Es ist etwas mehr View-bezogen, da es eine HtmlHelper-Erweiterung ist.

+0

Ja, diese machen definitiv mehr Sinn als die Zeichenkette.Format() jedes Mal in der Ansicht. Das Problem, mit dem ich konfrontiert bin, ist, dass das ViewModel oft an den Client für JavaScript-Verbrauch gerendert wird - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- zu-JavaScript-in-ASPNET-MVC.aspx oder während AJAX-Anforderungen. In diesen Fällen müsste ich die Formatierung auf der Client-Ebene vornehmen, was weniger als wünschenswert ist. Ich glaube, ich würde einen Haufen zusätzlicher Anstrengungen unternehmen, um alle Formatierungs-/Parsing-Probleme in einer Ebene zu trennen . –

+1

Es ist auch lästig für mich, dass MVC einen robusten Mechanismus zum Parsen von eingehenden Anfragen über benutzerdefinierte Model-Bindung, aber bietet nicht die gleiche Art von Erfahrung für die Formatierung während View Rendering. –

+0

Ich habe kein Problem mit der Ansicht oder dem Client, der die Formatierungsentscheidungen trifft. Generell bevorzuge ich es, dass der Controller oder das Modell wählt, wie man die Daten darstellt - das scheint das Prinzip der Trennung von Interessen zu verletzen. Was ist, wenn verschiedene Clients/Ansichten (z. B. Mobile oder Web) diese auf unterschiedliche Weise rendern möchten? – tvanfosson

3

Haben Sie darüber nachgedacht ein Displayformat auf Ihrem Ansichtsmodell setzen? Das ist was ich benutze und es ist schnell und einfach.

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


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