2009-12-31 3 views
6

Проект, над которым я работаю, имеет большое количество валютных свойств в модели домена, и мне нужно их форматировать как $#,###.## для передачи в представление и из представления. У меня были мысли о различных подходах, которые можно было бы использовать. Один из подходов может быть форматировать значения явно внутри вида, как и в "Pattern 1" from Steve Michelotti:ASP.NET MVC сопоставление ViewModel с пользовательским форматированием

... но это начинает нарушать DRY principle очень быстро.

Предпочитаемый подход заключается в том, чтобы делать форматирование во время сопоставления между DomainModel и ViewModel (согласно ASP.NET MVC in Action, раздел 4.4.1 и "Pattern 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>() 

... так что я могу свести к минимуму количества установки шаблонной для каждого из типы объектов.

Мне также интересно узнать о любых альтернативных стратегиях для выполнения пользовательского форматирования в представлении и из представления.


От ASP.NET MVC in Action:

Сначала мы могли бы попытаться передать этот простой объект прямо к зрения, но DateTime? свойства [в модели] вызовут проблемы. Например, нам нужно выбрать форматирование , например ToShortDateString() или ToString(). Вид будет вынужден сделать нулевое значение , чтобы сохранить экран с взорвать, когда свойства null. Мнения сложны для измерения , поэтому мы хотим сохранить их как можно более тонкие .Поскольку вывод изображения представляет собой строку, переданную в ответный поток , мы будем использовать только объекты, которые являются строчными; что - это объекты, которые никогда не сбой, если на них вызывается ToString(). Объект модели ConferenceForm - пример этого примера . В листинге 4.14 указано, что все свойства являются строками. Мы будем иметь даты правильно отформатирован перед этим видом модели объект находится в поле зрения данных. Этот способ , вид не должен учитывать объект , и он может правильно отформатировать .

+0

<% = string.Format ("{0: c}", Model.CurrencyProperty)%> выглядит симпатичным для меня. Может быть, я просто привык к этому ... –

ответ

2

Обычай TypeConverter является то, что вы ищете:

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

Затем создайте нейтрализатор:

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

Спасибо за ответ Джимми. Я рассмотрел использование TypeConverter , но проблема, которую я обнаружил в моем случае, заключается в том, что она будет применяться к * all * сопоставлениям от десятичной до строки. К сожалению, только некоторые из десятичных свойств являются валютой. Я думал о том, что, возможно, обертка вокруг десятичного числа (класс CurrencyDecimal: Decimal), но тогда я мог бы просто добавить неявные операции литья между типом и строкой. То, что я действительно хотел бы иметь, это нечто вроде TypeConverter, которое может проверять атрибуты свойств - возможно, я буду смотреть на это когда-нибудь, если оно не существует. –

6

Считаете ли вы использование метода расширения для форматирования денег?

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


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

Поскольку это явно вид связанных (не модели, связанные с) вопрос, я бы попытаться сохранить его в представлении, если вообще возможно. Это в основном переносит его на метод расширения на десятичный, но использование в представлении. Вы также можете сделать расширение HtmlHelper:

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


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

Если вам понравился этот стиль лучше. Это несколько больше связано с View, поскольку это расширение HtmlHelper.

+0

Да, это определенно имеет больше смысла, чем выполнение string.Format() каждый раз в представлении. Проблема, с которой я сталкиваюсь, заключается в том, что ViewModel часто будет передаваться клиенту для использования javascript - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- to-JavaScript-in-ASPNET-MVC.aspx или во время запросов AJAX. В этих случаях мне нужно будет сделать форматирование на клиентском уровне, что менее желательно - на самом деле, я чувствую, что поеду, хотя кучу дополнительных усилий, чтобы все проблемы форматирования/разбора были разделены на одном уровне , –

+1

Мне также неудобно, что MVC обладает надежным механизмом для анализа входящих запросов через пользовательскую привязку модели, но не обеспечивает такой же опыт для форматирования во время рендеринга View. –

+0

У меня нет проблем с представлением или клиентом, являющимся тем, кто принимает решения о форматировании. Обычно я предпочитаю, чтобы над контроллером или моделью выбирали, как представлять данные, что, по-видимому, нарушает принцип разделения интересов. Что делать, если разные клиенты/представления (например, mobile или web) хотят отображать его по-разному? – tvanfosson

3

Рассматривали ли вы положить DisplayFormat на вашем ViewModel? Это то, что я использую, и это быстро и просто.

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


View : 
    @Html.DisplayFor(m => m.CurrencyProperty) 
Смежные вопросы