Slauma
Slauma

Reputation: 177153

Display validation error when TimeSpan model binding exceptions occur

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:

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

Answers (2)

Slauma
Slauma

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

Chao
Chao

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

Related Questions