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!
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.
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With