4
votes

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?

5

5 Answers

3
votes

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)
        {
            ...
        }
    }
}
1
votes

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.

1
votes

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.

1
votes

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.

0
votes

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).