c# – 流畅的验证不接受带有千位分隔符的数字

前端之家收集整理的这篇文章主要介绍了c# – 流畅的验证不接受带有千位分隔符的数字前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个ASP.NET MVC 5项目与Fluent Validation for MVC 5.我也使用jQuery掩码插件自动添加数千到双重值.

在我的模型中:

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

猜你在找的C#相关文章