在我的模型中:
[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>
为包含模型定义流畅的验证器,但不包含任何规则.我仅使用服务器端验证.
public class MyModelValidator: AbstractValidator<MyModel> { public MyModelValidator() { } }
不幸的是,我收到的营业额验证错误如下:
我试过使用Model Binding来解决这个问题.但是,模型绑定器中的断点从不受到打击 – 流畅的验证似乎阻止了达到模型绑定的价值.
解决方法
>这个问题与Fluent Validation无关.我可以使用或不使用流畅的验证来重现/修复它.
>使用的DataFormatString不正确(缺少值占位符).应该是“{0:#,## 0}”.
> link的ModelBinder方法实际上是有用的.我猜你忘了写十进制数据类型,而你的模型使用双精度,所以你必须写入和注册另一个双重和双重?类型.
现在就这个问题.实际上有两个解决方案.他们都使用以下帮助类进行实际的字符串转换:
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,object>> { { typeof(byte),(s,c) => byte.Parse(s,NumberStyles.Any,c) },{ typeof(sbyte),c) => sbyte.Parse(s,{ typeof(short),c) => short.Parse(s,{ typeof(ushort),c) => ushort.Parse(s,{ typeof(int),c) => int.Parse(s,{ typeof(uint),c) => uint.Parse(s,{ typeof(long),c) => long.Parse(s,{ typeof(ulong),c) => ulong.Parse(s,{ typeof(float),c) => float.Parse(s,{ typeof(double),c) => double.Parse(s,{ typeof(decimal),c) => decimal.Parse(s,}; 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
这是链接方法的修改版本.它是一个单一的类来处理所有的数值类型和它们各自的可空类型:
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; } }
所有你需要的是在你的Application_Start中注册它:
protected void Application_Start() { NumericValueBinder.Register(); // ... }
自定义TypeConverter
这不是特定于ASP.NET MVC 5,但是DefaultModelBinder将字符串转换委托给相关联的TypeConverter
(类似于其他NET UI框架).实际上,这个问题是由于数值类型的默认TypeConverter类不使用Convert类,而Parse重载与NumberStyles
通过NumberStyles.Float排除NumberStyles.AllowThousands.
幸运的是System.ComponentModel提供了可扩展的Type Descriptor Architecture,它允许您关联一个自定义的TypeConverter.管道部分有点复杂(您必须注册一个定制的TypeDescriptionProvider
才能提供最终返回自定义TypeConverter的ICustomTypeDescriptor
实现),但是在提供的基类的帮助下,将大部分内容委托给底层对象,实现看起来像这样:
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,object value) { if (value is string) { try { return NumericValueParser.Parse((string)value,type,culture); } catch { } } return baseConverter.ConvertFrom(context,value); } } }
(是的,很多样板代码为了添加一个基本的线!另一方面,没有必要处理可空类型,因为DefaultModelBinder已经这样做:)
与第一种方法类似,您需要注意的是:
protected void Application_Start() { NumericTypeDescriptionProvider.Register(); // ... }