Reputation: 177153
In an ASP.NET MVC 4 application I have a view model that contains a nullable TimeSpan
property:
[DisplayName("My time")]
public TimeSpan? MyTime { get; set; }
It is bound to an input element in the view:
@Html.EditorFor(model => model.MyTime)
The input box gets rendered with the help of a custom editor template TimeSpan.cshtml
:
@model Nullable<System.TimeSpan>
@Html.TextBox("", (Model.HasValue
? Model.Value.ToString(@"hh\:mm") : string.Empty),
new { @class = "text-box single-line hasTimepicker" data_timepicker = true })
Now, if I enter the following two kinds of invalid times and submit the page I get the following different behaviour of the model binder:
If I enter a letter, say "a"
into the input element the ModelError
for this property when I drill into the ModelState.Values
collection has the ErrorMessage
property set to a message ("The value \"a\" for \"My time\" is invalid."
) and the Exception
property is null
. The bound value of MyTime
is null
.
This ErrorMessage
is displayed in the validation summary of the page.
If I enter an invalid time, say "25:12"
, into the input element the ModelError
for this property has the ErrorMessage
property set to an empty string but the Exception
property set to an exception of type InvalidOperationException
with an inner exception of type OverflowException
telling me that TimeSpan
could not be analysed because one of its numeric components is out of valid range. The bound value of MyTime
is null
.
Again, the ErrorMessage
is displayed in the validation summary of the page. But because it is empty it's not very useful.
Ideally for the second case of invalid input I would prefer to have the same kind of error message like for the first case, for example "The value \"25:12\" for \"My time\" is invalid."
.
How can I solve this problem?
Edit
A custom validation attribute apparently doesn't help here because it is not called for the invalid input in the examples above when already the model binder detects invalid values. I had tried that approach without success.
Upvotes: 0
Views: 1732
Reputation: 177153
@Chao's answer brought me on the right track to use a custom model binder.
Because I wanted to keep as much functionality of the default model binder (flexibility of entered formats, localization, etc.) as possible unchanged and only have a useful error message for the user in the case when he enters "25:12"
or similar I've created the following binder that just detects if the default model binder has added an OverflowException
(as an inner exception) to the model state and if yes I add an error message to the state:
public class TimeSpanModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
object timeSpanValue = base.BindModel(controllerContext, bindingContext);
var modelState = bindingContext.ModelState[bindingContext.ModelName];
var hasOverflowException = modelState.Errors
.Any(e => e.Exception != null &&
e.Exception.InnerException is OverflowException);
if (hasOverflowException)
{
var rawValues = modelState.Value.RawValue as string[];
if (rawValues != null && rawValues.Length >= 1)
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, string.Format(
"The value \"{0}\" for field \"{1}\" is invalid.",
rawValues[0],
bindingContext.ModelMetadata.GetDisplayName()));
}
}
return timeSpanValue;
}
}
Added in global.asax
/Application_Start()
to the ModelBinders
collection:
ModelBinders.Binders.Add(typeof(TimeSpan), new TimeSpanModelBinder());
ModelBinders.Binders.Add(typeof(TimeSpan?), new TimeSpanModelBinder());
Upvotes: 1
Reputation: 3043
The problem is that the error is happening at model binding and that's where you need to be catching and checking it.
I have a Timespan model binder and editor template for TimeSpan?
which should do what you need that's up on Gist
Upvotes: 2