views:

517

answers:

1

Hello to all! This is my first question here on stack overflow. i need help on a problem i encountered during an ASP.NET MVC2 project i am currently working on. I should note that I'm relatively new to MVC design, so pls bear my ignorance. Here goes : I have a regular form on which various details about a person are shown. One of them is "Date of Birth". My view is like this

<div class="form-items">
            <%: Html.Label("DateOfBirth", "Date of Birth:") %>
            <%: Html.EditorFor(m => m.DateOfBirth) %>
            <%: Html.ValidationMessageFor(m => m.DateOfBirth) %>
</div>

I'm using an editor template i found, to show only the date correctly :

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>"%>
<%= Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty))%>

I used LinqToSql designer to create my model from an sql database. In order to do some validation i made a partial class Person to extend the one created by the designer (under the same namespace) :

[MetadataType(typeof(IPerson))]
public partial class Person : IPerson
{        //To create buddy class    }

public interface IPerson
{
    [Required(ErrorMessage="Please enter a name")]
    string Name { get; set; }
    [Required(ErrorMessage="Please enter a surname")]
    string Surname { get; set; }
    [Birthday]
    DateTime? DateOfBirth { get; set; }
    [Email(ErrorMessage="Please enter a valid email")]
    string Email { get; set; }
}

I want to make sure that a correct date is entered. So i created a custom DataAnnotation attribute in order to validate the date :

public class BirthdayAttribute : ValidationAttribute
{
    private const string _errorMessage = "Please enter a valid date";

    public BirthdayAttribute() : base(_errorMessage) { }

    public override bool  IsValid(object value)
    {
        if (value == null)
        {
            return true;
        }
        DateTime temp;
        bool result = DateTime.TryParse(value.ToString(), out temp);
        return result;
    }
}

Well, my problem is this. Once i enter an incorrect date in the DateOfBirth field then no custom message is displayed even if use the attribute like [Birthday(ErrorMessage=".....")]. The message displayed is the one returned from the db ie "The value '32/4/1967' is not valid for DateOfBirth.". I tried to enter some break points around the code, and found out that the "value" in attribute is always null when the date is incorrect, but always gets a value if the date is in correct format. The same ( value == null) is passed also in the code generated by the designer.

This thing is driving me nuts. Please can anyone help me deal with this? Also if someone can tell me where exactly is the point of entry from the view to the database. Is it related to the model binder? because i wanted to check exactly what value is passed once i press the "submit" button. Thank you.

+1  A: 

Generally speaking all validation stuff is work after binder binded values. As you can understand it's not possible to bind dateTime value from string like "asdvfvk". So, when binder encounters with such an error it adds it to the ModelState (take a look at ModelState["DateOfBirth"].Errors[0].ErrorMessage), and binds default value. Default value for DateTime? is null, so that's why you always get it in IsValid method. It's normal. So as you can see validation for date has sence if you whant to check for example if it's bigger then some other date. If input string is incorrect no further verification have sence.

What can you do?

First straightforward way - you can correct your action like this

 [HttpPost]
        public ActionResult About(Person person, string dateOfBirth) {
            var birthdayAttribute = new BirthdayAttribute();
            if( !birthdayAttribute.IsValid(dateOfBirth)) {
                ModelState["DateOfBirth"].Errors.Clear();
                ModelState.AddModelError("DateOfBirth", birthdayAttribute.ErrorMessage);
            }
           .......
        }

As you can see there is string dateOfBirth, so binder have no problems with binding string value. But this will not make your users happy.

The better way - ensure that string will be in correct format with client Javascript. Actualy people use date picker controls for dates and feel themselves good.

In addition take a look here http://forums.asp.net/t/1512140.aspx Especialy Brad Wilson's answer.

er-v
Thank you very much for clarifying that the "problem"'s root is in the model bidding, andfor your suggestion. I read the link and also found this answer http://stackoverflow.com/questions/1538873/how-to-replace-the-default-modelstate-error-message-in-asp-net-mvc-2.I want to keep the validation inside my domain model(also in case no javascript is available).I guess i have to read more about model bidding and override its default behavior. Although your answer doesnt solve my problem, i check my question as answered, because you pointed me to the right direction. Any suggestions where to look?
goldenelf2
Well, the default binders behaviour is good. I think people from Microsoft have had some reasons to make it work like this :) The validation will be kept inside your domain model. Actualy ModelState will be invalid till a. User will input all strings in correct format (binder responsibility)b. All validations are fine (validatros responsibility. presented in domain model)Binder do a greate job for you. Actualy he does for you the thing you want to do in validator. You want the date be in correct format - but it's already done by binder. There is no reason to do it again.
er-v
User will get message, that string he have entered is in incorrect format before he will enter it in correct format - and you, as as a programmer can be shure that if you are expecting for the DateTime - binder will make user to input a string wich can be parsed to DateTime. And it's all done on server. JavaScript has the only purpose - to help to form users input in correct format faster. But if JavaScript is off on the client - it does not metters, becouse binder will make user to do everething right (or user will go away).
er-v
So the short summary: You should not override binder's behaviour. Why do you want to do string parse for yourself, when it's already done for you? It's not right. Use JavaScript just to make user's life better - validation will still be kept on server, in your domain model. Look at MvcContrib to understand reasons to override the binder. If you are looking for a good book - Mvc In Action is good.
er-v
Again er-v thank very much for your time! You are right about everything you are saying.The only reason i want to override it is to use custom messages, and in the future localized strings for the validation errors. By digging more, i found a more elegant solution, so that i can keep my controller free of validation and without needing to make my own bidder. It is funny because i found a solution to this, in the first ever site i read about mvc design. I'm talking about Nerddinner: http://nerddinnerbook.s3.amazonaws.com/Part3.htm
goldenelf2
By calling the model bidder explicitly, they can catch its default model bidding behavior and add messages to a collection with RuleViolation objects. So i guess if i intervene in the process and check which field it bids, then i can add custom messages to the ModelState. I'll have to try that as soon as i have some coffee :) thank you again for your time and your suggestions!
goldenelf2