Is the DataTypeAttribute validation working in MVC2?

13,142

Solution 1

[DataType("EmailAddress")] doesn't influence validation by default. This is IsValid method of this attribute (from reflector):

public override bool IsValid(object value)
{
    return true;
}

This is example of custom DataTypeAttribute to validate Emails (taken from this site http://davidhayden.com/blog/dave/archive/2009/08/12/CustomDataTypeAttributeValidationCustomDisplay.aspx):

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple  = false)]
public class EmailAddressAttribute : DataTypeAttribute
{
    private readonly Regex regex = new Regex(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.Compiled);

    public EmailAddressAttribute() : base(DataType.EmailAddress)
    {

    }

    public override bool IsValid(object value)
    {

        string str = Convert.ToString(value, CultureInfo.CurrentCulture);
        if (string.IsNullOrEmpty(str))
            return true;

        Match match = regex.Match(str);   
        return ((match.Success && (match.Index == 0)) && (match.Length == str.Length));
    }
}

Solution 2

Like LukLed pointed out, DataTypeAttribute doesn't do any validation by default. But it does influence templates regarding how the data is presented.

For example if you call Html.DisplayFor() method on a model that has DataType(DataType.EmailAddress) attribute, it'll format its value with <a href="mailto:{0}">{0}</a> (at least in MVC RC2).

Solution 3

Alternatively, you can directly use RegularExpression attribute on your field instead of creating your own attribute that is in the end going to check for a regex matching.

[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = PaErrorMessages.InvalidEmailAddress)]
public string Email { get; set; }

Solution 4

Starting from .NET 4.5 there is EmailAddressAttribute, which has a correct implementation of the IsValid method. So if you're targeting .NET 4.5, then for validation please consider using the EmailAddressAttribute instead of a custom one. For example,

public class Model
{
    [EmailAddress(ErrorMessage = "INVALID EMAIL")]
    public string Email {get; set;}
}

And if you're curious about the implementation of EmailAddressAttribute, then here is the decompiled (using JetBrains dotPeek decompiler) source of the class:

using System;
using System.ComponentModel.DataAnnotations.Resources;
using System.Text.RegularExpressions;

namespace System.ComponentModel.DataAnnotations
{
  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
  public sealed class EmailAddressAttribute : DataTypeAttribute
  {
    private static Regex _regex = new Regex("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled);

    static EmailAddressAttribute()
    {
    }

    public EmailAddressAttribute()
      : base(DataType.EmailAddress)
    {
      this.ErrorMessage = DataAnnotationsResources.EmailAddressAttribute_Invalid;
    }

    public override bool IsValid(object value)
    {
      if (value == null)
        return true;
      string input = value as string;
      if (input != null)
        return EmailAddressAttribute._regex.Match(input).Length > 0;
      else
        return false;
    }
  }
}

Solution 5

Check out Scott Guthrie's blog post on MVC 2 validation. It is excellent. http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx

Share:
13,142
wenqiang
Author by

wenqiang

A happy programmer :)

Updated on June 06, 2022

Comments

  • wenqiang
    wenqiang almost 2 years

    As far as I know the System.ComponentModel.DataAnnotations.DataTypeAttribute not works in model validation in MVC v1. For example,

    public class Model
    {
      [DataType("EmailAddress")]
      public string Email {get; set;}
    }
    

    In the codes above, the Email property will not be validated in MVC v1. Is it working in MVC v2?

  • wenqiang
    wenqiang about 14 years
    Thanks for point out that DataTypeAttribute is not a validation attribute.
  • LukLed
    LukLed about 14 years
    @Wayne: It actually is ValidationAttribute. It inherits from ValidationAttribute, but always returns true. You can override IsValid method and define your own, as it is in David Hayden's solution.
  • Çağdaş Tekin
    Çağdaş Tekin about 14 years
    @LukLed, It indeed inherits from ValidationAttribute. I wonder why. Anyway, will correct that part.
  • LukLed
    LukLed about 14 years
    DataAnnotationsModelValidator goes through every ValidationAttribute. DataTypeAttribute is ValidationAttribute, so it could be easily inherited and validation introduced. When you define your own data type, you can easily add validation.
  • Çağdaş Tekin
    Çağdaş Tekin about 14 years
    @LukLed, Thanks. I edited the answer again to delete the part where it said it's not used for validation.
  • Mike Scott
    Mike Scott about 14 years
    I think you want to make that regex static because setting the compiled option may cause compilation to be done everwhere the attribute is used and, if memory serves, generating a new in-memory assembly each time.
  • LukLed
    LukLed about 14 years
    @Mike Scott: New in-memory assembly? What do you mean by that?
  • Mike Scott
    Mike Scott almost 14 years
    LukLed, what it says on the tin - an assembly created on the fly in memory ;-) Regex with the compiled option emits IL into an in-memory assembly. So to be sure you don't use up memory with each invocation, you should save the compiled regex instance and reuse it. see blogs.msdn.com/b/bclteam/archive/2004/11/12/256783.aspx.
  • skrebbel
    skrebbel over 12 years
    @Mike Scott: you're right, but if I'm not mistaken, an attribute is only instantiated once per class, not per object. This means that in practice the cost is pretty small. Nevertheless you're right that there's no cost in it being static. Should've been a const if the language would allow.
  • Mike Scott
    Mike Scott over 12 years
    @skrebbel, so if you put the attribute on 10 classes, doesn't that mean 10 instantiations of the attribute and therefore 10 in-memory assemblies created for the same regex expression?
  • skrebbel
    skrebbel over 12 years
    Sure thing! But not 10 in-memory assemblies per class instance. Still a significant difference. But yeah, good point :)
  • Mike Scott
    Mike Scott about 12 years
    @skrebbel, absolutely! But then I did say everywhere the attribute is used - you mentioned instances, not me ;-) If you put the attribute without the regex declared static on 10 classes, you'll create 10 in-memory assemblies instead of 1. The real point here is that over-ambitious use of compiled regexes can use a lot of memory and even cause memory leaks. You have to be careful to declare them statically in most cases.
  • KSwift87
    KSwift87 over 10 years
    @LukLed -- The link is now broken.