Having some trouble creating a time-only input field with unobtrusive validation, mapping to a non-nullable DateTime field.
I am working with a database-first approach. The database has datetime fields used to store both dates and times. By that I mean some datetimes have real date data and 00:00:00 for time while others have meaningless date data with real times. I understand this came about due to limitations with EF5 and C# datatypes. Now using EF6, but not particularly intending to change the database.
Here is the view model
[UIHint("Date")]
public DateTime MatchDate { get; set; }
[UIHint("Time")]
public DateTime StartTime { get; set; }
The date-only field, MatchDate, is working. It uses an EditorFor template, Date.cshtml
(below). It allows text input, correctly attaches a jQuery datepicker and correctly validates server side and client-side with unobtrusive.
@model DateTime
@if (Model == DateTime.MinValue)
{
@Html.TextBox("", "", new { @class = "datepicker" })
}
else
{
@Html.TextBox("", String.Format("{0:d/M/yyyy}", Model), new { @class = "datepicker" })
}
So I created a new EditorFor template for Time.cshtml
@model DateTime
@if (Model == DateTime.MinValue)
{
@Html.TextBox("", "", new { @class = "timepicker" })
}
else
{
@Html.TextBox("", String.Format("{0:h\\:mm tt}", Model), new { @class = "timepicker" })
}
At this point the Html input element gets an unwanted data-val-date
attribute, causing unobtrusive date validation. I found that by adding [DataType(DataType.Time)]
, the data-val-date
is no longer added. While this is a good thing, I'm not if that is exactly (and only) what the datatype attribute is supposed to do. So this is the first part of the question.
Next, I have created custom validation rules (server & client-side) for a 12-hr time format.
Annotation:
public class TimeTwelveAttribute : ValidationAttribute, IClientValidatable
{
private const string _ErrorMessage = "Invalid time format. Please use h:mm am/pm (TimeAttribute)";
public TimeTwelveAttribute()
{
this.ErrorMessage = _ErrorMessage;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = _ErrorMessage,
ValidationType = "timetwelve"
};
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
DateTime time;
if (value == null || !DateTime.TryParse(value.ToString(), out time))
{
return new ValidationResult(_ErrorMessage);
}
return ValidationResult.Success;
}
}
javascript
$.validator.unobtrusive.adapters.addSingleVal("timetwelve");
$.validator.addMethod(
"timetwelve",
function (value, element) {
return this.optional(element) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(value);
},
"Invalid time format. Please use h:mm am/pm"
);
However, adding the [TimeTwelve]
data annotation to StartTime has no effect:
- Server-side validation is occurring, but it's creating a different error message that I can't override: "The value 'asdf' is not valid for Start Time."
- There is no client-side validation message popping up if I type 'asdf' in to the time field.
I gather this is caused by the postback values being mapped in to C# DateTimes. Whatever mechanism does that takes a higher priority and overrides all other validation. As an exercise, I rewrote StartTime as a string (and updated the templates etc.) and sure enough [TimeTwelve]
works, client-side and server-side as expected. I feel like this is aspect has not been covered in any of the related questions here on stackoverflow.
So the second part of my question, given that I would really prefer to use DateTimes in my view model, how can I get validation to work?
This is the closest question I could find, but is from 2013.