2016-11-01 3 views
10

MVC 5 용 Fluent Validation이있는 ASP.NET MVC 5 프로젝트가 있습니다. 또한 jQuery 마스킹 플러그인을 사용하여 자동으로 수천 개의 double 값을 추가합니다. 내가 가지고있는보기에서Fluent Validation이 천 단위 구분 기호가있는 숫자를 허용하지 않습니다.

[Display(Name = "Turnover")] 
    [DisplayFormat(ApplyFormatInEditMode = true,ConvertEmptyStringToNull =true,DataFormatString ="#,##0")] 
    public double? Turnover { get; set; } 

: 모델에서

나는이 유창 검증이 포함 된 모델에 대해 정의

<th class="col-xs-2"> 
    @Html.DisplayNameFor(model=>model.Turnover) 
</th> 
<td class="col-xs-4"> 
    @Html.TextBoxFor(model => model.Turnover, new { @class = "form-control number", placeholder="Enter number. Thousands added automatically" }) 
</td> 
<td class="col-xs-6"> 
    @Html.ValidationMessageFor(model => model.Turnover, "", new { @class = "text-danger" }) 
</td> 

을하지만 아무 규칙이 없습니다. 서버 측 유효성 검사 만 사용하고 있습니다. 나는이 문제를 해결하기 위해 Model Binding를 사용하여 시도 enter image description here

을 다음과 같이

public class MyModelValidator: AbstractValidator<MyModel> 
{ 
    public MyModelValidator() 
    { 

    } 
} 

불행하게도 나는 매출에 대한 유효성 검사 오류가 발생합니다. 그러나 모델 바인더의 중단 점은 절대로 타격을 입지 않습니다. 유창한 유효성 검사는 값이 모델 바인더에 도달하지 못하게합니다.

+1

유형을 문자열로 변경 한 다음 이중 필드 인 백킹 필드를 사용하고 getter 및 setter가 변환을 수행하도록 고려 했습니까? XAML 응용 프로그램에서 변환기를 사용하는 것처럼 처리하는 것이 더 좋은 방법 인 것처럼 느껴지지만이 컨텍스트에서이를 수행하는 방법을 모르거나 ...이 문제를 해결할 수 있습니다. http : // stackoverflow. com/questions/29975128/asp-net-mvc-data-annotation-for-currency-format – Sinaesthetic

+0

FluentValidation은 어디에서 사용할 수 있습니까? 'ModelState'의 오류입니까, 아니면 AbstractValidator가 오류를 일으 킵니까? – krillgar

+0

빈 유효성 검사기 코드를 추가했습니다 – gls123

답변

4

몇 가지 언급 :

  • 문제는 유창함 검증과 아무런 공통점이 없다. Fluent Validation의 유무에 관계없이 재현/수정이 가능했습니다.
  • 사용 된 DataFormatString이 잘못되었습니다 (값 자리 표시자가 누락되었습니다). 실제로는 "{0:#,##0}"이어야합니다.
  • link에서 ModelBinder 접근 방식이 실제로 작동합니다. 나는 모델이 double?을 사용하는 동안 decimal 데이터 형식으로 작성되었다는 것을 잊었을 것입니다. 따라서 doubledouble? 유형에 대해 다른 하나를 작성하고 등록해야합니다.

이제 주제가됩니다. 사실 두 가지 해결책이 있습니다. 둘 다 실제 문자열 변환에 대해 다음 헬퍼 클래스를 사용

using System; 
using System.Collections.Generic; 
using System.Globalization; 

public static class NumericValueParser 
{ 
    static readonly Dictionary<Type, Func<string, CultureInfo, object>> parsers = new Dictionary<Type, Func<string, CultureInfo, object>> 
    { 
     { typeof(byte), (s, c) => byte.Parse(s, NumberStyles.Any, c) }, 
     { typeof(sbyte), (s, c) => sbyte.Parse(s, NumberStyles.Any, c) }, 
     { typeof(short), (s, c) => short.Parse(s, NumberStyles.Any, c) }, 
     { typeof(ushort), (s, c) => ushort.Parse(s, NumberStyles.Any, c) }, 
     { typeof(int), (s, c) => int.Parse(s, NumberStyles.Any, c) }, 
     { typeof(uint), (s, c) => uint.Parse(s, NumberStyles.Any, c) }, 
     { typeof(long), (s, c) => long.Parse(s, NumberStyles.Any, c) }, 
     { typeof(ulong), (s, c) => ulong.Parse(s, NumberStyles.Any, c) }, 
     { typeof(float), (s, c) => float.Parse(s, NumberStyles.Any, c) }, 
     { typeof(double), (s, c) => double.Parse(s, NumberStyles.Any, c) }, 
     { typeof(decimal), (s, c) => decimal.Parse(s, NumberStyles.Any, c) }, 
    }; 

    public static IEnumerable<Type> Types { get { return parsers.Keys; } } 

    public static object Parse(string value, Type type, CultureInfo culture) 
    { 
     return parsers[type](value, culture); 
    } 
} 

사용자 정의 IModelBinder

을이 링크 된 접근 방식의 수정 된 버전입니다.

protected void Application_Start() 
{ 
    NumericValueBinder.Register(); 
    // ... 
} 

사용자 정의 TypeConverter

이를 : 당신이 필요로하는 모든이 Application_Start에 등록하는 것입니다

using System; 
using System.Web.Mvc; 

public class NumericValueBinder : IModelBinder 
{ 
    public static void Register() 
    { 
     var binder = new NumericValueBinder(); 
     foreach (var type in NumericValueParser.Types) 
     { 
      // Register for both type and nullable type 
      ModelBinders.Binders.Add(type, binder); 
      ModelBinders.Binders.Add(typeof(Nullable<>).MakeGenericType(type), binder); 
     } 
    } 

    private NumericValueBinder() { } 

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     var modelState = new ModelState { Value = valueResult }; 
     object actualValue = null; 
     if (!string.IsNullOrWhiteSpace(valueResult.AttemptedValue)) 
     { 
      try 
      { 
       var type = bindingContext.ModelType; 
       var underlyingType = Nullable.GetUnderlyingType(type); 
       var valueType = underlyingType ?? type; 
       actualValue = NumericValueParser.Parse(valueResult.AttemptedValue, valueType, valueResult.Culture); 
      } 
      catch (Exception e) 
      { 
       modelState.Errors.Add(e); 
      } 
     } 
     bindingContext.ModelState.Add(bindingContext.ModelName, modelState); 
     return actualValue; 
    } 
} 

: 그것은 모든 숫자 유형과 각각의 nullable 형식을 처리하는 하나의 클래스입니다 ASP.NET MVC 5에만 국한되는 것은 아니지만 DefaultModelBinder은 문자열 변환을 연결된 TypeConverter (다른 NET UI 프레임 워크와 비슷 함)에 위임합니다. 실제로이 문제는 숫자 형식에 대한 TypeConverter 클래스가 Convert 클래스를 사용하지 않고 을 제외한 NumberStyles을 전달하는 Parse 오버플로로 인해 발생합니다.

다행스럽게도 System.ComponentModel은 확장 가능 Type Descriptor Architecture을 제공하여 맞춤 TypeConverter을 연결할 수 있습니다. 배관 부분은 다소 복잡합니다 (ICustomTypeDescriptor 구현을 제공하기 위해 사용자 정의 TypeDescriptionProvider을 등록해야합니다. 사용자 정의 TypeConverter을 최종적으로 반환합니다).하지만 대부분의 내용을 기본 객체에 위임하는 제공된 기본 클래스의 도움으로 구현 다음과 같습니다

using System; 
using System.ComponentModel; 
using System.Globalization; 

class NumericTypeDescriptionProvider : TypeDescriptionProvider 
{ 
    public static void Register() 
    { 
     foreach (var type in NumericValueParser.Types) 
      TypeDescriptor.AddProvider(new NumericTypeDescriptionProvider(type, TypeDescriptor.GetProvider(type)), type); 
    } 

    readonly Descriptor descriptor; 

    private NumericTypeDescriptionProvider(Type type, TypeDescriptionProvider baseProvider) 
     : base(baseProvider) 
    { 
     descriptor = new Descriptor(type, baseProvider.GetTypeDescriptor(type)); 
    } 

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) 
    { 
     return descriptor; 
    } 

    class Descriptor : CustomTypeDescriptor 
    { 
     readonly Converter converter; 
     public Descriptor(Type type, ICustomTypeDescriptor baseDescriptor) 
      : base(baseDescriptor) 
     { 
      converter = new Converter(type, baseDescriptor.GetConverter()); 
     } 
     public override TypeConverter GetConverter() 
     { 
      return converter; 
     } 
    } 

    class Converter : TypeConverter 
    { 
     readonly Type type; 
     readonly TypeConverter baseConverter; 
     public Converter(Type type, TypeConverter baseConverter) 
     { 
      this.type = type; 
      this.baseConverter = baseConverter; 
     } 
     public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 
     { 
      return baseConverter.CanConvertTo(context, destinationType); 
     } 
     public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 
     { 
      return baseConverter.ConvertTo(context, culture, value, destinationType); 
     } 
     public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 
     { 
      return baseConverter.CanConvertFrom(context, sourceType); 
     } 
     public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 
     { 
      if (value is string) 
      { 
       try { return NumericValueParser.Parse((string)value, type, culture); } 
       catch { } 
      } 
      return baseConverter.ConvertFrom(context, culture, value); 
     } 
    } 
} 

(예, 순서 상용구 코드를 많이 한 필수 줄을 추가하기 위해 다른 측면에서, DefaultModelBinder 이미 않기 때문에 nullable 형식을 처리 할 필요가 없습니다

: 그!

첫 번째 접근법과 마찬가지로, 등록 만하면됩니다.

protected void Application_Start() 
{ 
    NumericTypeDescriptionProvider.Register(); 
    // ... 
} 
+0

이 모든 기술을 시도했지만 FluentValidation으로 작업 할 수 없었습니다. 그러나 세부 사항에 대해서는 현상금의 절반이 적절하다고 생각합니다. FluentValidation은 pull 요청으로 변경해야한다고 생각합니다. – gls123

+0

아무런 문제가 없지만 나는 현상금에 신경 쓰지 않는다. 그러나 문제가 해결되지 않았다는 사실을 듣고 슬프다. FluentValidation 패키지를 설치하고 설정을했는데 그 차이점을 보지 못했습니다. 위의 후크가 없으면 문제가 중복됩니다. 그들과 함께 - 고정.예를 들어,'EditorFor'를 사용하면, 위의 수정은'int'와 비슷한 (그러나'decimal','double'과'float'을 위해 작동합니다)에서는 작동하지 않을 수 있습니다. 그러나'TextBoxFor'는 모든 유형에 적용됩니다. 그게 단서 일 수도 있습니다. 모르겠습니다. 보고있을 것입니다, 원인/해결책을 찾으면 게시하십시오. 행운을 빕니다! –

+0

고마워요 - 그럴 수도 있습니다 - EditorForFor를 TextBoxFor로 전환해야합니다. – gls123

1

문화권 문제 일 수 있습니다. 클라이언트 측 (10,000,000 -> 10.000.000)에서 쉼표 대신 점을 사용하거나 서버 측에서 문화권 문제를 수정하십시오.

2

FluentValidation에서는 문제가 발생하지 않지만 MVC의 모델 바인딩은 double 유형으로 바인딩됩니다. MVC의 기본 모델 바인더는 숫자를 구문 분석 할 수 없으며 falseIsValid에 할당합니다.

다음 코드를 포함하면 문제가 해결되었습니다. credits to this post.

public class DoubleModelBinder : System.Web.Mvc.DefaultModelBinder { 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { 
     var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (result != null && !string.IsNullOrEmpty(result.AttemptedValue) 
      && (bindingContext.ModelType == typeof(double) || bindingContext.ModelType == typeof(double?))) { 
      double temp; 
      if (double.TryParse(result.AttemptedValue, out temp)) return temp; 
     } 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

그리고

Application_Start에 아래 라인을 포함한다 :
ModelBinders.Binders.Add(typeof(double), new DoubleModelBinder()); 
ModelBinders.Binders.Add(typeof(double?), new DoubleModelBinder()); 

가 명시 적으로 this post에서와 현재의 문화를 알리는 것이 좋습니다.

+0

나는 이것을 시험 할 것이다. – gls123

+0

@ gls123 작동하는지 여부를 알려주십시오. –

+0

작동하지만 정수가 아님 – gls123