Blog
Simple Time Picker model binder for ASP.NET MVC 3, and how to save it to SQL CE 4 using EF 4.1
There’s plenty of DatePickers out there, like, for instance, the ubiquitous jQuery DatePicker, but how about Time pickers?
Well if you’re looking for a fancy, flashy, drop-dead-gorgeous Time picker control… this post isn’t for you. What this post will teach you, dear reader, is how to simply and effectively use two Dropdowns to to model bind hours and minutes to a TimeSpan. This will be the jaw-dropping end result:
The Model and our constraints
We’ll set up a very simple model because in this case all I care about is saving the time. Let’s say we have an Appointment class, with only an ID, a name and a time:
using System;
using System.ComponentModel.DataAnnotations;
public class Appointment
{
public int ID { get; set; }
public string Name { get; set; }
public long Ticks { get; set; }
[NotMapped]
public TimeSpan Time
{
get { return TimeSpan.FromTicks(Ticks); }
set { Ticks = value.Ticks; }
}
}
There’s several things going on here. As you can see, I’ve split the representation of time into two properties. The Time property itself is calculated from the Ticks property (an inherent property of the TimeSpan struct). It is also marked with the NotMappedAttribute, which will instruct Entity Framework to not map this property to the database. Instead, we’ll save the Ticks property to the database and reconstruct the TimeSpan from the ticks every time.
Why make it so hard? It appears that SQL Server Compact 4 does not declare any datatype to represent a TimeSpan. Hence our workaround. To be honest, I haven’t tried this in SQL Server (the Big Boy edition, I mean), so it could be that you can get away mapping the TimeSpan directly. This will definitely work on all versions though.
The EditorTemplate
Next we’ll define what the standard look of our “time picker” will be. Even though we’re saving the Ticks property to the database we’ll use the Time property for display. The type of the property is TimeSpan so that’s how we’ll have to name our EditorTemplate. Create a new folder in the Shared views folder of your application, call it “EditorTemplates” and add a partial view called TimeSpan.cshtml (or TimeSpan.ascx if you’re using the WebFormsViewEngine instead of Razor). You folder structure should look like this:
This is what TimeSpan.cshtml will look like in order to render the 2 Dropdowns depicted above:
@model TimeSpan
@Html.DropDownList("", Enumerable.Range(0, 24)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Hours == i })) :
@Html.DropDownList("", Enumerable.Range(0, 60)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Minutes == i }))
In a nutshell, we make TimeSpan.cshtml a partial view typed to TimeSpan, because that is the type of the Time property, then we “generate” 2 DropDownLists, the first one with the values 0 to 23 (24 is the upper bound and is not included) for the hours and 0 to 59 for the minutes.
Calling the template
In order to tell ASP.NET MVC to render our Time property using the TimeSpan Editor template we just need to use the EditorFor Html helper. So suppose we have a view called Add in an AppointmentController:
@model Appointment
@using(Html.BeginForm()) {
// more fields here...
@Html.EditorFor(model => model.Time)
<input type="submit" value="Hit me!" />
}
That’s it really. ASP.NET MVC will take care of the dirty work of trying to find an Editor Template that matches the type of the Time property. Now we just need to bind the value of the two Dropdowns into a single TimeSpan.
Black magic
Well, not really of course; simple model binding at work. Create a new class and call it, for instance, TimeBinder. Our editor template will provide us with 2 values once we post the form, one for each Dropdown. The work of TimeBinder will be to get the 2 values and return a TimeSpan:
public class TimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
// Ensure there's incomming data
var key = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider
.GetValue(key);
if (valueProviderResult == null ||
string.IsNullOrEmpty(valueProviderResult
.AttemptedValue))
{
return null;
}
// Preserve it in case we need to redisplay the form
bindingContext.ModelState
.SetModelValue(key, valueProviderResult);
// Parse
var hours = ((string[])valueProviderResult.RawValue)[0];
var minutes = ((string[])valueProviderResult.RawValue)[1];
// A TimeSpan represents the time elapsed since midnight
var time = new TimeSpan(Convert.ToInt32(hours),
Convert.ToInt32(minutes), 0);
return time;
}
}
Final setup
Now that we have a binder in place we need to instruct the application to use it. Add the following line to the Global.asax.cs file, in the Application_Start method:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(TimeSpan), new TimeBinder());
// route registration code here
}
DropDownLists? Meh.
I hear you saying, isn’t it more convenient to just type the hours and minutes yourself? Why use tedious Dropdowns?
You’re probably right. The beauty of what we’ve just done is that we now only need to change the TimeSpan.cshtml partial view to display two TextBoxes instead of DropDownLists and it will just work!
Don’t believe me? Here’s the new TimeSpan.cshtml:
@model TimeSpan
@Html.TextBox("", Model.Hours) :
@Html.TextBox("", Model.Minutes)
ASP.NET MVC will give the TextBoxes the same name it gave the Dropdowns, which means our TimeBinder will still be able to fetch the values from the ValueProvider and parse them. Quite an example of loose-coupling your presentation layer from your business layer if you ask me! :)
Of course, without the Dropdown limiting the range of values a user may input you’d better restrict those TextBoxes to numerical values only, otherwise you’re up for some fancy runtime errors ;)
Any questions? Let me know!
10 comment(s)
Very good solution. Really helped me.
Many Thanks.
Very slick. I'm liking MVC more and more. Thanks for the post.
@Martin,
Well spotted, thanks! I'll fix the article a.s.a.p.
Thank you all guys for your comments! :)
I got the same isue as Martin, so I tried naming the dropdown, but now I get an error in the TimeBinder because there's only one item in the valueProviderResult.RawValue array.
Any help would be appreciated.
ditto to Jason's problem
Thanks Sergi, I was struggling on what to do with TimePickers.
I did run into some problems along the lines of Jason and Martin though. I've used this as a basis though and come up with a version that binds to nullable TimeSpans.
It still needs some work but will remember your separate values, has error highlighting on the individual drop downs and handles wrong input like you'd get if you used textboxes.
I've put it up on GitHub if anyone wants to have a look or make some changes.
https://gist.github.com/1671586
tooooooooooooooooooooooo goooooood
loved it
What would be the best way to implement scenario when I want to allow user to leave the time field blank.
And then how do I check if the time field was left blank when user hits submit button ?
Thanks!

Thanks this is a lifesaver, a very elegant solution. Much better than what I orginally came up with.
One small change I needed to make to your example was to set the name e.g.
@Html.DropDownList("Hours", Enumerable.Range(0, 24)...
Otherwise you'd always get the same hours and minutes when it posted back.
Cheers