2009-12-31 6 views
6

내가 작업중인 프로젝트에는 도메인 모델에 많은 통화 속성이 있으며보기와주고 받기 위해이 형식을 $#,###.##으로 필요로합니다. 나는 사용할 수있는 다른 접근 방식에 대한 견해를 가지고있었습니다.ASP.NET MVC 사용자 지정 서식이있는 ViewModel 매핑

... 그러나 이것은 매우 빠르게 DRY principle을 위반 시작 : 한 가지 방법은 "Pattern 1" from Steve Michelotti 같이 뷰 내에서 명시 적으로 값을 포맷 할 수 있습니다.

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

를 사용하여 시도 그리고 그것을 매핑했습니다

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

...하지만 난 그 기분이 테스트를 만족

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

속성이 동일한 이름을 가지고 있기 때문에 ResolveUsing을 수행 한 후 FromMember으로 매핑되는 속성을 명시 적으로 정의 할 필요가 없습니다. 이 매핑을 정의하는 방법은? 앞에서 언급했듯이이 방식으로 매핑해야하는 통화 값이있는 속성이 많이 있습니다.

그런데 - 전 세계적으로 일부 규칙을 정의하여 이러한 매핑을 자동으로 해결할 수있는 방법이 있습니까? 뷰 모델 속성이 이미 DataAnnotation로 장식되어 검증을위한 [DataType(DataType.Currency)] 속성, 그래서 내가 않는 몇 가지 규칙을 정의 할 수 있다고 기대했다 : I는 각 보일러 설치의 양을 최소화 할 수 있도록 ...

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에서


:

는 처음에 우리는 바로 보기에이 간단한 객체하지만, 날짜 시간을 통과 유혹 할 수 있는가? 모델에서 [ ] 속성을 사용하면 문제가 발생할 수 있습니다. 예를 들어 ToShortDateString() 또는 ToString()과 같이 형식을 선택해야합니다. 보기는 속성이 일 때 화면이 계속 날아 오지 않도록 확인하기 위해 강제로 null이됩니다. 조회수는 단위로하기가 어려우므로 최대한 얇게 유지해야합니다. . 보기의 결과는 응답 스트림으로 전달되는 문자열이므로 개의 문자열 전용 인 객체 만 사용합니다. 그 은, ToString()이 호출 될 때 결코 실패하지 않는 객체입니다. ConferenceForm 뷰 모델 개체는 입니다. 4.14를 나열하면 모든 속성이 문자열임을 알 수 있습니다. 이보기 모델 개체가보기 데이터에 배치되기 전에 우리는 적절하게 날짜를 으로 지정합니다. 이 방법,보기는 개체를 고려할 필요가 없으며 정보를 올바르게 형식화 할 수 있습니다.

+0

<% = string.Format ("{0 : c}", Model.CurrencyProperty) %> 어쩌면 난 그냥 익숙해 져있을거야 ... –

답변

2

사용자 지정 TypeConverter를 당신이 찾고있는 무엇인가? 그것은 내가 사용하는 것이며 빠르고 간단합니다.

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


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

지미에게 답장을 보내 주셔서 감사합니다. TypeConverter 를 사용하여 보았습니다. 그러나 제 경우에 발견 한 문제는 십진수에서 문자열로 모든 * 매핑에 적용된다는 것입니다. 불행하게도 소수 속성 중 일부만 통화입니다. 아마도 decimal 주위에 래퍼를 만들 생각을했습니다. (클래스 CurrencyDecimal : Decimal), 그렇다면 유형과 문자열간에 암시 적 캐스트 작업을 쉽게 추가 할 수있었습니다. 내가 정말로 갖고 싶은 것은 속성 속성을 검사 할 수있는 TypeConverter와 같은 것입니다. 아마도 그것이 존재하지 않는다면 언젠가이 글을 쓸 것입니다. –

6

돈을 형식화하는 확장 방법을 사용 해본 적이 있습니까?

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


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

이것은 분명히보기와 관련된 (모델 관련이 아닌) 문제이기 때문에 가능하면보기에 유지하려고합니다. 이것은 기본적으로 10 진수의 확장 메서드로 이동하지만 사용법은 뷰에 있습니다. HtmlHelper 확장 프로그램을 사용할 수도 있습니다.

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


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

그 스타일이 더 좋았 으면. HtmlHelper 확장 기능이기 때문에 뷰와 관련이 있습니다.

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

예, 분명히 string.Format()을 수행하는 것보다 더 의미가 있습니다. 내가 직면 한 문제는 ViewModel이 종종 자바 스크립트 소비를 위해 클라이언트에 렌더링된다는 것입니다 - ala http://www.trycatchfail.com/blog/post/2009/12/22/Exposing-the-View-Model- ASPNET-MVC.aspx 또는 AJAX 요청 중. 이 경우에는 클라이언트 레이어에서 포맷을 수행해야합니다. 클라이언트 레이어는 바람직하지 않습니다. 사실 모든 포맷팅/파싱 문제를 하나의 레이어로 구분하기 위해 많은 노력을 기울일 것이라고 생각합니다. . –

+1

MVC는 사용자 지정 모델 바인딩을 통해 들어오는 요청을 구문 분석하는 견고한 메커니즘을 가지고 있지만 View 렌더링 중에 동일한 형식의 경험을 제공하지 않는다는 사실도 나에게 안타깝다. –

+0

뷰 또는 클라이언트가 형식을 결정할 때 문제가되지 않습니다. 일반적으로 데이터를 표현하는 방법을 선택하는 컨트롤러 나 모델을 선호합니다. 이는 우려 원칙의 분리를 위반하는 것으로 보입니다. 다른 클라이언트/뷰 (예 : 모바일 및 웹)가 다른 방식으로 렌더링하려는 경우 어떻게해야합니까? – tvanfosson

3

당신이 당신의 ViewModel에 DisplayFormat을두고 생각 해 봤나 :

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

그런 다음 컨버터를 만듭니다

관련 문제