Проект, над которым я работаю, имеет большое количество свойств валюты в модели предметной области, и мне нужно отформатировать их как $#,###.##
для передачи в представление и из него. У меня были мысли о различных подходах, которые можно было бы использовать. Одним из подходов может быть явное форматирование значений внутри представления, как в "Шаблон 1" от Стива Мичелотти :
... но это очень быстро начинает нарушать принцип DRY.
По-видимому, предпочтительным подходом является выполнение форматирования во время сопоставления между DomainModel и ViewModel (согласно ASP.NET MVC в Действие, раздел 4.4.1 и "Шаблон 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));
}
... Но я чувствую, что мне не нужно явно определять, какое свойство сопоставляется с FromMember
после выполнения ResolveUsing
, поскольку свойства имеют одно и то же имя - есть ли лучший способ определить это сопоставление? Как я уже упоминал, существует большое количество свойств со значениями в валюте, которые необходимо отобразить таким образом.
При этом - есть ли способ, которым я мог бы автоматически разрешить эти сопоставления, определив какое-то правило глобально? Свойства ViewModel уже украшены 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>()
... так что я могу свести к минимуму количество стандартных настроек для каждого из типов объектов.
Мне также интересно узнать о любых альтернативных стратегиях выполнения пользовательского форматирования в представлении и обратно.
Сначала у нас может возникнуть соблазн передать этот простой объект прямо в представление, но метод DateTime? свойства [в модели] вызовут проблемы. Например, нам нужно выбрать для них форматирование, такое как ToShortDateString() или ToString(). Представление будет вынуждено выполнять проверку на нуль, чтобы экран не взрывался, когда свойства равны нулю. Представления сложно тестировать, поэтому мы хотим, чтобы они были как можно тоньше. Поскольку вывод представления — это строка, передаваемая в поток ответов, мы будем использовать только те объекты, которые совместимы со строками; то есть объекты, которые никогда не перестанут работать при вызове ToString(). Примером этого является объект модели представления ConferenceForm. Обратите внимание, что в листинге 4.14 все свойства являются строками. У нас будут правильно отформатированные даты, прежде чем этот объект модели представления будет помещен в данные представления. Таким образом, представлению не нужно учитывать объект, и оно может правильно форматировать информацию.