ASP.NET MVC UpdateModel()参数转换失败

时间:2011-08-23 18:24:22

标签: c# asp.net-mvc validation asp.net-mvc-2 data-annotations

预警:我对MVC及其范例以及它的一些内部工作相对较新,但我对它很满意。这是我的第二个基础MVC应用程序,我对如何解决我们的一个测试人员发现的“问题”感到有点困惑。

用户获得的是一个编辑屏幕,其中给出了来自财政部的每日LIBOR费率的生效日期(百分比)。速率始终在0到100之间,因此我尝试使用我的一个域对象的元数据中的RangeAttribute来约束该范围。我已经指定了这样的范围:

[Required, DisplayName("Published Rate"), Range(typeof(decimal), "0", "100")]
public object PublishedRate { get; set; }

请注意,我传入字符串值,因为RangeAttribute没有重载的构造函数,它带有小数。这似乎很有效,直到用户进入并输入一些与众不同的东西,例如:

  

“0.000000000000000000000000000000001”

这会导致UpdateModel()失败; ModelState显示此错误(对于相同的ModelState值,好奇地说是三次):

  

从“System.String”类型到“System.Decimal”类型的参数转换失败。

深入研究错误揭示了原因。下面的第一行是该领域验证报告的内容。我发现很奇怪,这并没有冒充模型验证错误(即未显示在模型的摘要验证列表中):

  

“0.000000000000000000000000000000001不是十进制的有效值。”   “对于十进制,值太大或太小。”

System.Web.Mvc.ValueProviderResult.ConvertSimpleType()System.ComponentModel.BaseNumberConverter.ConvertFrom()正在抛出异常。

用户永远不会输入这样的值,但我不介意知道是否有任何内置于MVC的机制可以或将会阻止这种情况(服务器端,即)。对于像下面这样的数字似乎没有问题,它似乎只是打破了非常小的数字。

  

“0.555555555555555555555555555555555”

在一天结束时,我真的只需要9位精度。支持这些值的数据库表列是decimal(9,6)。我知道我可以为我的模型实现自定义模型绑定器并手动从Request中收集值,但是必须有一些更容易的东西,例如自定义FilterAttribute或者可以在尝试之前纠正值的东西为了与模型绑定,我只是不确定是什么,并且正在寻找建议。

我似乎记得在阅读有关尝试使用RangeAttribute约束小数值的一些问题,但我不记得这个问题。也许你们那里的MVC大师可以对这种情况有所了解。

2 个答案:

答案 0 :(得分:0)

您可以使用Regex属性来包含精度为9的小数。这也允许您在Regex失败时添加自定义消息,例如“您的值可能在小数点后最多有9个位置。 “或类似的东西。此外,如果启用了客户端验证,则Regex将在客户端和服务器端验证中工作。

答案 1 :(得分:0)

因此,经过几个小时的敲击,我决定使用以下自定义模型绑定器来获取小数。它确保所有十进制值在绑定之前都是可解析的。

public class TreasuryIndexRateDecimalBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var providerResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (providerResult != null)
        {
            decimal value;
            if (!decimal.TryParse(providerResult.AttemptedValue, NumberStyles.Float, CultureInfo.CurrentCulture, out value))
            {
                // TODO: Decide whether to show an error
                // bindingContext.ModelState.AddModelError(bindingContext.ModelName, "error message");
                return 0m;
            }
            return Math.Round(value, 6);
        }
        return base.BindModel(controllerContext, bindingContext);
    }
}

绑定在Application_Start()中设置,以便为所有小数值注册。

protected void Application_Start()
{
    ModelBinders.Binders.Add(typeof(decimal), new TreasuryIndexRateDecimalBinder());

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

除非有人提出更有趣的方法,否则我认为我会坚持这一点。