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