2009-12-31 17 views
6

El proyecto en el que estoy trabajando tiene una gran cantidad de propiedades de moneda en el modelo de dominio y necesito formatearlas como $#,###.## para transmitir hacia y desde la vista. He tenido una opinión acerca de diferentes enfoques que podrían usarse. Un enfoque podría ser para dar formato a los valores explícitamente dentro de la vista, como en "Pattern 1" from Steve Michelotti:Asignación de ASP.NET MVC ViewModel con formato personalizado

... pero esto empieza a violar DRY principle muy rápidamente.

El enfoque preferido parece ser hacer el formateo durante la asignación entre DomainModel y un ViewModel (según ASP.NET MVC in Action sección 4.4.1 y "Pattern 3"). Usando AutoMapper, esto se traducirá en un código como el siguiente:

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

usando IValueFormatter esta manera funciona muy bien. Ahora, ¿cómo mapearlo desde el DomainModel a ViewModel? He intentado usar una costumbre 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); 
} 
} 

Y entonces mapeado con:

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

que satisfaga esta prueba:

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

... Pero me siento que No debería necesitar definir explícitamente de qué propiedad se está mapeando con FromMember después de hacer ResolveUsing ya que las propiedades tienen el mismo nombre. ¿Hay alguna mejor opción? forma de definir este mapeo? Como mencioné, hay un buen número de propiedades con valores monetarios que deberán correlacionarse de esta manera.

Dicho esto, ¿hay alguna manera de que estas asignaciones se resuelvan automáticamente definiendo alguna regla globalmente? Las propiedades ViewModel ya están decoradas con DataAnnotation atributos [DataType(DataType.Currency)] para su validación, así que esperaba que pudiera definir una regla que hace:

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

... de modo que pueda minimizar la cantidad de configuración repetitivo para cada una de las tipos de objetos

También estoy interesado en conocer las estrategias alternativas para lograr el formato personalizado desde y hacia la Vista.


De ASP.NET MVC in Action:

En un primer momento podría estar tentado a pasar este simple objeto directamente a la vista , pero el DateTime? propiedades [en el Modelo] causará problemas. Por ejemplo, tenemos que elegir un formato para ellos, como ToShortDateString() o ToString(). La vista se vería forzada a hacer cero comprobando para mantener la pantalla de explotando cuando las propiedades son nulo. Las vistas son difíciles de probar con la unidad , por lo que queremos mantenerlas tan delgadas como sea posible.Como el resultado de una vista es una cadena que se pasa a la corriente de respuesta , solo usaremos los objetos que son aptos para cadenas; que es, objetos que nunca fallarán cuando se llame a ToString(). El objeto de modelo de vista ConferenceForm es un ejemplo de esto. Observe en el listado 4.14 que todas las propiedades son cadenas. Tendremos las fechas correctamente formateadas antes de que este objeto de vista se coloque en los datos de la vista. Esta forma , la vista no necesita considerar el objeto , y puede formatear la información correctamente.

+0

<% = string.Format ("{0: c}", Model.CurrencyProperty)%> me parece bonita. Tal vez estoy acostumbrado ... –

Respuesta

2

Un TypeConverter es lo que está buscando:

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

A continuación, cree el convertidor:

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

Gracias por la respuesta Jimmy. He analizado el uso de TypeConverter , pero el problema que encontré en mi caso es que se aplicará a * todas las asignaciones de decimal a cadena. Desafortunadamente, solo algunas de las propiedades decimales son moneda. Pensé en hacer un contenedor alrededor de decimal - (clase CurrencyDecimal: Decimal), pero luego podría agregar tan fácilmente operaciones de conversión implícitas entre el tipo y la cadena. Lo que realmente me gustaría tener es algo así como TypeConverter que pueda examinar los atributos de las propiedades; tal vez busque escribir esto alguna vez, si no existe. –

6

¿Ha considerado utilizar un método de extensión para formatear dinero?

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


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

Dado que este es claramente un tema (relacionado con el modelo no) relacionados con vistas, que iba a tratar de mantenerlo en la vista si es posible. Esto básicamente lo mueve a un método de extensión en decimal, pero el uso está en la vista. También podría hacer una extensión HtmlHelper:

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


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

Si le gustó ese estilo mejor. Es algo más relacionado con la Vista, ya que es una extensión HtmlHelper.

+0

Sí, definitivamente tienen más sentido que hacer la cadena. Formatear() cada vez en la Vista. El problema que estoy enfrentando es que el ViewModel a menudo se procesará para el consumo de javascript - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- to-JavaScript-in-ASPNET-MVC.aspx o durante las solicitudes AJAX. En estos casos, necesitaría hacer el formateo en la capa del cliente, lo cual es menos que deseable; de ​​hecho, creo que me tomaría un montón de esfuerzo adicional para tener todas las preocupaciones de formateo/análisis separadas en una capa . –

+1

También es molesto para mí que MVC tenga un mecanismo robusto para analizar las solicitudes entrantes a través del enlace de modelo personalizado, pero no proporciona el mismo tipo de experiencia para formatear durante la representación de la vista. –

+0

No tengo un problema con la vista o el cliente es el que toma las decisiones de formateo. En general, prefiero que el controlador o el modelo elijan cómo representar los datos, lo que parece violar el principio de separación de preocupaciones. ¿Qué pasa si diferentes clientes/vistas (móvil vs. web, por ejemplo) quieren renderizarlo de diferentes maneras? – tvanfosson

3

¿Ha considerado poner un formatSalida en su modelo de vista? Eso es lo que uso y es rápido y simple.

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


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