Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of Validation When Using Annotations and Custom Attributes

I've noticed that while creating a custom validation attribute, my validation only fires after native MVC data annotations fire. Is there any way it could fire "at the same time"?

To show what I mean, pretend I have this form:

FirstName: <FirstName Textbox>
LastName: <LastName TextBox>
Zip: <Zip TextBox>

So I have a [Required] annotation for all 3, but in addition, for the Zip property, I have a custom attribute. If the user DOESN'T enter a firstname or lastname, but enters an invalid Zip (which my attribute should validate this), there should be an error message on all three - but there isn't. There's only an error on firstName and lastName.

This is the code:

Person.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

// My validator
using MvcApplication3.Extensions.Validation;

namespace MvcApplication3.Models
{
  public class Person
  {
    [Required(ErrorMessage="Field required!")]
    public string firstName{get;set;}

    [Required(ErrorMessage="Field required!")]
    public string lastName { get; set; }    

    [Zip(ErrorMessage="You gotta put in a valid zip code")]
    [Required(ErrorMessage="Field required!")]
    public string zipCode { get; set; }    
  }
}

Controller:

[HttpPost]
public ActionResult Index(FormCollection form, Person person)
{
  return View(person);
}  

View:

@model MvcApplication3.Models.Person
@{
  ViewBag.Title = "Person";
  Layout = "~/Views/Shared/_Layout.cshtml";       

}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<h2>
  Testing Form: @Model.firstName
</h2>
<hr />

@{Html.EnableClientValidation();}

@using (Html.BeginForm())
{ 
  @Html.LabelFor(model => model.firstName) 
  @Html.TextBoxFor(model => model.firstName) 
  @Html.ValidationMessageFor(model=>model.firstName)

  <br /><br />
  @Html.LabelFor(model => model.lastName) 
  @Html.TextBoxFor(model => model.lastName) 
  @Html.ValidationMessageFor(model=>model.lastName)

  <br /><br />
  @Html.LabelFor(model => model.zipCode) 
  @Html.TextBoxFor(model => model.zipCode) 
  @Html.ValidationMessageFor(model=>model.zipCode)    

  <br /><br />
  <input type="submit" value="Submit" />
}

Zip Validator (Zip.cs):

  public class ZipAttribute : ValidationAttribute
  {
    public override bool IsValid(object value)
    {
      bool foundMatch = false;
      try
      {
        foundMatch = Regex.IsMatch(value.ToString(), "\\A\\b[0-9]{5}(?:-[0-9]{4})?\\b\\z");
      }
      catch (ArgumentException ex)
      {
        // Syntax error in the regular expression
      }
      return foundMatch;
    }
  }

Also, I know I can do this with Regexp data annotation, but I'm looking to roll my own custom validators in the future.

Thanks!

like image 423
SaltProgrammer Avatar asked May 12 '11 23:05

SaltProgrammer


People also ask

Is data annotation client side validation?

Note: By default, the validation done using Data Annotation attributes is Server Side. And hence to make it work Client Side, the Client Side validation must be enabled.

What are custom data annotation attributes?

Data annotations are attributes we can find in the System. ComponentModel. DataAnnotations namespace. These attributes provide server-side validation and the framework also supports client-side validation.


1 Answers

There's a better solution than disabling unobtrusive client validation.

Since you're only matching a regular expression, you might try doing this instead (will work with javascript validation):

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class ZipAttribute : System.ComponentModel.DataAnnotations.RegularExpressionAttribute
{
    public ZipAttribute() : base("\\A\\b[0-9]{5}(?:-[0-9]{4})?\\b\\z")
    {
        ErrorMessage = "Invalid ZIP code.";
    }
}

and in Global.asax:

        DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ZipAttribute), typeof(RegularExpressionAttributeAdapter));

What's nice about doing it this way, you can specify your own default Error Messages!

Weird enough, some of the validation attributes (StringLength, Range, RegularExpression) still use AttributeAdapters, while other attributes such as the CompareAttribute uses the IClientValidatable.

Good luck!

like image 107
Schalk Avatar answered Oct 31 '22 00:10

Schalk