我有一个使用Fluent Validation for MVC 5的ASP.NET MVC 5项目。我还使用jQuery masking插件自动添加数千个双值。
在模型中我有:
[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来解决此问题。但是模型绑定器的断点永远不会受到打击 - 流畅的验证似乎阻止了达到模型绑定器的价值。
答案 0 :(得分:5)
很少提及:
DataFormatString
不正确(缺少值占位符)。它应该是"{0:#,##0}"
。ModelBinder
方法确实有效。我猜您忘了它是为decimal
数据类型编写的,而您的模型使用double?
,因此您必须为double
和double?
类型编写并注册另一个现在谈论这个话题。实际上有两种解决方案。它们都使用以下辅助类进行实际的字符串转换:
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
这是链接方法的修改版本。它是一个处理所有数字类型及其各自可空类型的类:
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
的{{3}}实现,但是在提供的基类的帮助下将大部分内容委托给底层对象,实现如下:
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
已经这样做了:)
与第一种方法类似,您只需要注册它:
protected void Application_Start()
{
NumericTypeDescriptionProvider.Register();
// ...
}
答案 1 :(得分:3)
问题不在于FluentValidation,而在于MVC的模型绑定到double
类型。 MVC的默认模型绑定器无法解析数字并将false
分配给IsValid
。
在我添加以下代码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中的当前文化。
答案 2 :(得分:1)
这可能是一种文化问题。尝试在客户端使用点而不是逗号(10,000,000 - > 10.000.000)或修复服务器端的文化问题。