views:

981

answers:

6

I'm trying to figure out the best way to validate a one page checkout. It contains :

  • ship address
  • billing address
  • etc.

the Address class obvious contains First Name, Last Name, Street1, Street2, City, State, Zip, Phone etc.

Lets say the user clicks 'OK' before entering anything - then you end up with a dozen or more validation errors giving you a large block of red text that just looks ugly.

I'd like to validate the address as a single entity, and give an intelligent error - such as 'incomplete address', or more specific errors when appropriate. But I still want to be able to highlight each individual field that has a problem. I can't see an easy way to do this right now, because obviously the Html.ValidationSummary helper will show every field.

So I want to show the summary as:

 "Your shipping address is incomplete"

and highlight in red Zip and City.

I think I'd have to do a completely custom ValidationSummary, and maybe even a completely custom datastructure.

Do any validation frameworks make such a summary easier to do, where the summary should show an intelligent summary and not just every individual field error.


Edit: MVC 2 RC now supports model-level errors.

ValidationSummary now supports overloads where only model-level errors are displayed. This is useful if you are displaying validation messages inline next to each form field. Previously, these messages would be duplicated in the validation summary. With these new changes, you can have the summary display an overall validation message (ex. “There were errors in your form submission”) as well as a list of validation messages which don’t apply to a specific field.

Anybody got an actual sample of how to do this?

+1  A: 

IDataErrorInfo has two members:

  • Error - Gets an error message indicating what is wrong with this object.
  • Item - Gets the error message for the property with the given name.

If you implement Error member, you'll have one error message.

LukLed
but then the individual fields don't get highlighted. i dont want to lose that visual cue - especially since not all fields are actually required (such as Address2)
Simon_Weaver
@Simon: If you want field to be highlighted, you can add empty error: `if (String.IsNullOrEmpty(address.Zip)) ModelState.AddModelError("Zip", "");` It will not be shown in validation summary.
LukLed
+1  A: 

I deal with a similar problem in a recent project, i did a custom Validation Summary, here is the code:

<%
      if (!ViewData.ModelState.IsValid)
       {
           Response.Write("<div class=\"prepend-1 span-10 last notice\">");
           Response.Write("<span>Please fix fields marked with an asteristk <span class=\"ss_sprite ss_asterisk_orange\"> </span></span>");
           Response.Write("<ul>");
           foreach (KeyValuePair<string, ModelState> keyValuePair in ViewData.ModelState)
           {
               foreach (ModelError modelError in keyValuePair.Value.Errors)
               {
                %>
                <li><%= Html.Encode(modelError.ErrorMessage)%></li>
                <%
       }
           } Response.Write("</ul>");
           Response.Write("</div>");
       }
    %>

I did it in a Partial View, but maybe is better wrap this in a HTML Helper method, just like the original ValidationSummary.

Inside you can check for any special and unique requirements. Hope it Helps.

Omar
did you do the grouping i speak of? i suppose i could make errors i don't want to display just "*" in the error message and filter those out in this list
Simon_Weaver
I didn't post the complete code because its proprietary source, but within the foreach you can check for that specific case, check if this properties Street1, Street2, City, State, are all empty, then skip their specific messages an just send one instead like "Your shipping address is incomplete", you will still be able to highlight each individual field, because the modelState still have the specific items with errors, but the message display will be just one.
Omar
I admit this is like the quick, not maintainable and dirty way, but if you are in a rush it just works =).If time is not a problem, take a look at the IDataErrorInfo.
Omar
A: 

Check out this alternative use of the IDataErrorInfo interface - it may shed some light on how to have a single message for multiple fields.

cottsak
Why the down vote?
cottsak
i got a down vote too, maybe its because now there is an open bounty on the question, this really bother me, the people just around here down voting questions with no reason or comment.
Omar
+1  A: 

Here is what I would do:

Put your validation errors into ModelState whichever way is the most comfortable to you. You can add them directly to ModelState in your controller, using IDataErrorInfo, or with DataAnnotations and a validation runner. It doesn't really matter as long as you populate ModelState with your errors and re-display the view.

Then, make sure all your inputs also have a corresponding Html.ValidationMessage() associated with them in your form:

<%= Html.TextBox("city") %>
<%= Html.ValidationMessage("city", "*") %>

Depending on your css rules for the validation error classes this will turn the text-box red and display a red asterisk next to it telling the user they need to correct their input.

Finally, since you're not interesting in displaying the full validation summary just do a simple check to see if ModelState is valid, and if not display your generic message.

<% if (!ViewData.ModelState.IsValid) { %>
    <div id="validation-message">Your Shipping Address in Incomplete</div>
<% } %>

This solution will highlight the specific fields that the user has incorrectly filled out and display a short description of the errors like you want.

DM
+2  A: 

You could go with a composite Address property and validate the whole address as a unit:

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string Zip { get; set; }
}

public class Order
{
    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [AddressRequired("Your shipping address is incomplete")]
    public Address ShipTo { get; set; }

    [AddressRequired("Your billing address is incomplete")]
    public Address BillTo { get; set; }

    // you could do this if you still need 1:1 mapping for model binding
    public string ShippingCity
    {
        get { return ShipTo.City; }
        set { ShipTo.City = value; }
    }
}

And the validation attribute would look something like this:

public class AddressRequiredAttribute : ValidationAttribute
{
    ...

    public override bool IsValid(object value)
    {
        var address = value as Address;

        if (address != null)
        {
            ...
        }
    }
}
Michael Valenty
Hadn't looked into ValidationAttribute before. Thanks - http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.aspx
Simon_Weaver
Having a dedicated Address class could have additional benefits. For example, you could override ToString(), or put a Format() method on it to display the address back during confirmation. Also, it's reusable for a BillTo address field. Hope it helps!
Michael Valenty
wierd - it won't let me accept your answer (or any of these ones)
Simon_Weaver
Thanks for trying
Michael Valenty
A: 

Scottgu just released a great blog post on the new validation features.

While it doesn't go into depth on how to implement model-level validation it points to the default ASP.NET MVC 2 application project template as an explanation of how to do this:

In addition to creating validation attributes that apply to individual properties on an object, you can also apply validation attributes at the class level – which allows you to perform validation logic across multiple properties within an object. For an example of this in action, you can review the “PropertiesMustMatchAttribute” custom attribute that is included in the AccountModels.cs/vb file within the default ASP.NET MVC 2 application project template (just do a File->New ASP.NET MVC 2 Web Project within VS 2010 and look for this class).

Simon_Weaver