2

In our ASP.NET MVC 4 application, one of the models has a field of the DateTime type. When editing such model objects via a form, the value for the DateTime field has to be non-empty and on the format yyyy-MM-dd H:mm:ss (e.g., 2012-10-17 10:49:00). How do I ensure this field is correctly validated in the application? I've tried the following annotations:

[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.DisplayFormat(DataFormatString="yyyy-MM-dd H:mm:ss",
        ApplyFormatInEditMode=true)]

However, validation of form data doesn't require all components of the format to be present. For instance, the value '2012-10-17' is accepted (leaving out the 'H:mm:ss' part). It's just verified that the field contains a valid DateTime string.

How should I ensure that this DateTime field is indeed on my specified format (yyyy-MM-dd H:mm:ss)?

aknuds1
  • 65,625
  • 67
  • 195
  • 317
  • This question addresses DateTime validation on the server side: http://stackoverflow.com/questions/5390403/datetime-date-and-hour-validation-with-data-annotation. – aknuds1 Oct 17 '12 at 12:04

2 Answers2

1

Alternative solution - view-only model class

Darin's solution is of course valid, but it's not the only one you can use. And it would require you to write more complex code than with this solution that I'm going to show you here.

So this is an alternative. I'd suggest that instead of creating a custom model binder you rather create a separate view model class that instead of taking DateTime takes a string where you can set as complex validation regular expression as you like. And then have a method on it that would translate it to your application/domain model class instance (and back).

// suppose this app model
public class User
{
    [Required]
    public string Name { get; set; }

    [Required]
    public DateTime DateOfBirth { get; set; }
}

public class ViewUser
{
    [Required]
    public string Name { get; set; }

    [Required]
    [RegularExpression("\d{4}-\d{2}-\d{2}(?:\s\d{1,2}:\d{2}:\d{2})?")]
    public string DateOfBirth { get; set; }

    public ViewUser(User user)
    {
        this.Name = user.Name;
        this.DateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd H:mm:ss");
    }

    public User ToPoco()
    {
        return new User {
            Name = this.Name,
            DateOfBirth = DateTime.Parse(this.DateOfBirth, "yyyy-MM-dd H:mm:ss")
        };
    }
}

With a bit of tweaking you could inherit ViewUser from User class and use new keyword on DateOfBirth and use base property to store correct typed value. In that case you wouldn't need the ToPoco method.

Note: you will have to use DateTime.TryParseExact method to parse your dates because they may include time or they may not. I didn't include that in my code because it depends on the exact requirements of your date input.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • Is there some way to tie into the standard JavaScript-driven form validation though? The current client side validation detects if I enter a non-datetime string, I'd just need to change this validation to follow my specification for a valid DateTime. – aknuds1 Oct 17 '12 at 10:23
  • Ah, I see you suggest RegularExpressionAttribute to tie into the validation system. I guess that'd make sense, although I'd prefer a specialized DateTime validator! – aknuds1 Oct 17 '12 at 10:29
  • @aknuds1: what do you mean by *a specialised DateTime validator*? What else than format would you like to validate with these values? My regular expression is very simple so it also accepts values like 9999-99-99 99:99:99 which is of course invalid, but it can easily be edited to provide correct dates and times only. **So what else would you like to test about your date and time inputs?** – Robert Koritnik Oct 17 '12 at 10:39
  • A DateTime validator, as opposed to a RegularExpression validator, that accepts the same kind of format strings as the DateTime class (e.g., "yyyy-MM-dd H:mm:ss"). There is already DateTime validation built in, as I can't submit an invalid DateTime string in my form, but I'm not allowed to specify the format AFAICT. – aknuds1 Oct 17 '12 at 10:48
  • @aknuds1: Depends how you look at it. But having separate view models in particular pats is nothing unusual and is sometimes encouraged. **Example:** you have `User` entity in your app, but then you also have a *User registration* form. This one uses different set of fields even though it maps to the same app entity. In this case you'd also create a view model that would have **two** password properties and set different validation rules over it. The same would be true for *Change password* form. Also points to the same entity, but view model is different (3 password properties). – Robert Koritnik Oct 17 '12 at 12:55
  • @aknuds1: But if you think that Darin's solution suits you better, you should implement that one and accept his answer. – Robert Koritnik Oct 17 '12 at 12:56
  • @aknuds1: Regarding your `DateTime` validator. This would not work, because validators work on result type instances. So if your value is already `DateTime` then provided string format was apparently ok. You have to intervene before that conversion. `DateTime` validator would therefore only be able to validate particular `DateTime` values (ranges, minimums, maximums, comparisons to other dates etc.) Provided string format is beyond its capabilities. That's done by model binders or the way I've shown you. – Robert Koritnik Oct 17 '12 at 12:59
  • By not ideal, I'm thinking of evaluating a regular expression over a DateTime format string. It's fine otherwise. A DateTime validator would have to work on string fields, I've tried my hand at one in the meantime, downside is it's server side only – aknuds1 Oct 17 '12 at 13:17
  • 1
    @akunuds: No no! You can implement client-side validation as well. Your custom validator has to implement `IClientValidatable` interface... Check this Darin's answer about it: http://stackoverflow.com/a/4747466/75642 – Robert Koritnik Oct 17 '12 at 14:13
  • 2
    Interesting, will give it a go when back from vacation next week. Thanks. – aknuds1 Oct 17 '12 at 15:59
  • 1
    I tried the IClientValidatable approach, it was even easier than expected, considering JavaScript is involved. Thanks! – aknuds1 Oct 31 '12 at 12:25
0

You could write a custom model binder which will use the exact format you have specified in the DisplayFormat attribute. I have shown an example of how this could be achieved in this post.

Also don't be confused into thinking that the DisplayFormat attribute overrides the Required attribute. The DisplayFormat is only used for displaying the field in the input field. It is not a validation attribute. It has strictly nothing to do with validation and when the form is POSTed to the server it is never used.

Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • You're right, I was confused about DisplayFormat :) I had made the additional mistake in the meantime of applying attributes to the model's interface instead (as I'd done this for JSON.Net attributes previously), which was why validation wasn't working. I'll consider your custom data binding strategy. – aknuds1 Oct 17 '12 at 10:20