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!
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!
You need to add a Javascript version of your validation that will run client-side (or disable client-side validation, but that's a bit naff).
There's a sample of building custom validation for email addresses here:
http://thepursuitofalife.com/asp-net-mvc-3-unobtrusive-javascript-validation-with-custom-validators/
This shows the C# code (which includes setting the javascript function name that will do the client-side validation) as well as the javascript "validemail" routine.
public class ValidEmailAttribute : ValidationAttribute, IClientValidatable
{
// ...
public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "validemail"
};
}
}
And the JS:
$(function() {
jQuery.validator.addMethod("validemail", function (value, element, param) {
var emailPattern = /^[a-zA-Z0-9._-]+@@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
return emailPattern.test(value);
});
jQuery.validator.unobtrusive.adapters.addBool("validemail");
});
The reason this is happening is because you have unobtrusive client side validation enabled, and your custom validation attribute doesn't implement IClientValidatable
. It would need to implement this to allow the rendering of data-*
attributes which are needed as part of the client validation process. You would also need to provide a client side regex validation routine that mirrors you server side validation.
If you want to go the easy route, disable client side validation and unobtrusive javascript in web.config like so:
<appSettings>
<add key="ClientValidationEnabled" value="false"/>
<add key="UnobtrusiveJavaScriptEnabled" value="false"/>
</appSettings>
Your page should then behave how you would expect, but all your validation will now occur on the server. If you want to give the unobtrusive client side validation a whirl, then these links should helpful.
- Unobtrusive Client Validation in ASP.NET MVC 3
- Unobtrusive custom validator for MVC3
精彩评论