预警:我对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大师可以对这种情况有所了解。
答案 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);
}
除非有人提出更有趣的方法,否则我认为我会坚持这一点。