Some decimal
and decimal?
properties in my view model are marked as "Percent" data type, along with other data annotations, for example:
[DataType("Percent")]
[Display(Name = "Percent of foo completed")]
[Range(0, 1)]
public decimal? FooPercent { get; set; }
I'd like to permit the user some flexibility in how they enter the data, i.e. with or without the percent sign, intermediate spaces, etc. But I still want to use the DefaultModelBinder
behavior to get all of its functionality such as checking the RangeAttribute
and adding the appropriate validation messages.
Is there a way to parse and change the model value, then pass it along? Here is what I am trying, but am getting a runtime exception. (Ignore the actual parsing logic; this is not its final form. I'm just interested in the model replacement question at this point.)
public class PercentModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.DataTypeName == "Percent")
{
ValueProviderResult result =
bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (result != null)
{
string stringValue =
(string)result.ConvertTo(typeof(string));
decimal decimalValue;
if (!string.IsNullOrWhiteSpace(stringValue) &&
decimal.TryParse(
stringValue.TrimEnd(new char[] { '%', ' ' }),
out decimalValue))
{
decimalValue /= 100.0m;
// EXCEPTION : This property setter is obsolete,
// because its value is derived from
// ModelMetadata.Model now.
bindingContext.Model = decimalValue;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
Never mind, this was a fundamental misunderstanding of where validation happens in the MVC cycle. After spending some time in the MVC source code, I see how this works.
In case it is helpful to others, here is what is working for me:
[DataType("Percent")]
[Display(Name = "Percent of foo completed")]
[Range(0.0d, 1.0d, ErrorMessage="The field {0} must be between {1:P0} and {2:P0}.")]
public decimal? FooPercent { get; set; }
And in the binder, you just return the value:
public class PercentModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.DataTypeName == "Percent")
{
ValueProviderResult result =
bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (result != null)
{
string stringValue =
(string)result.ConvertTo(typeof(string));
decimal decimalValue;
if (!string.IsNullOrWhiteSpace(stringValue) &&
decimal.TryParse(
stringValue.TrimEnd(new char[] { '%', ' ' }),
out decimalValue))
{
return decimalValue / 100.0m;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With