victor victor - 9 days ago 5
C# Question

MVC 5 custom validation attribute not triggered

So I've been banging my head against the wall here. I followed the tutorials to the letter and I have no ideia why this custom validation isn't working. It is supposed to validate phone numbers.

for shorter go to the end

Custom attribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CustomPhoneAttribute : DataTypeAttribute, IClientValidatable
{
private static readonly string[] ddds = new[] { ... };

private static Regex regex = new Regex(@"^(\([0-9]{2}\)) ([0-9]{5}-[0-9]{4}|[0-9]{4}-[0-9]{4}$", RegexOptions.Compiled);

private const string DefaultErrorMessage = "{0} must be a phone number.";

public CustomPhoneAttribute()
: base(DataType.PhoneNumber)
{
ErrorMessage = DefaultErrorMessage;
}

public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name);
}

private bool ValidatePhone(string phone)
{

if (regex.Match(phone).Success)
{
var ddd = phone.Substring(1, 2);

if (ddds.Contains(ddd))
{
var phoneParts = phone.Substring(5).Split('-');
}

// TODO: perform further evaluation base on
// ddd, first digit and extra 9.
// For now we only check the ddd exists.

return true;
}

return false;
}

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

return ValidatePhone((string)value);
}

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "brphone"
};

}


Model:

{...}

[CustomRequired] // this works.
[DataType(DataType.PhoneNumber)]
[Display(Name = "Phone")]
[CustomPhone]
public string Phone { get; set; }

{...}


Javascript:

$.validator.addMethod("brphone",

function (value, element) {

if (!this.optional(element)) {

// perform validation.
return true;
}

return true;
});

$.validator.unobtrusive.adapters.addBool("brphone");


View:

@Html.TextBoxFor(m => m.Phone, new { @class = "form-control phonemask-client", placeholder = "(xx) xxxxx-xxxx" })


Everything seems to check out, still neither server side nor client side validation is working.

FIGURED PART OF IT OUT (possibly bug from microsoft)

If I remove the regex object from the data annotation class, it works like a charm. Now, why that happens, I have no idea! If I attach a debugger, when it hits the regex declaration and I click step over to continue debugging, the program just returns to the web page and nothing happens.

Is it forbidden to use regex inside a data annotation?

Answer

You're missing a parentheses in your regular expression, which is causing an error when the attribute is instantiated.

// missing ')' between the {4} and $
private static Regex regex = new Regex(@"^(\([0-9]{2}\)) ([0-9]{5}-[0-9]{4}|[0-9]{4}-[0-9]{4})$", RegexOptions.Compiled); 

We don't see an error at runtime due to the static keyword on the field.

Let's pretend that your attribute looked like this:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CustomPhoneAttribute : DataTypeAttribute
{
    public static string MyString = GetValue();

    public CustomPhoneAttribute()
        : base(DataType.PhoneNumber)
    {
    }

    private static string GetValue()
    {
        throw new Exception();
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        return base.IsValid(value, validationContext);
    }
}

When using the static keyword, the runtime will need to initialize the type-level values at some point in time before the class itself is instantiated. However, when this happens it causes a crash, because the GetValue code is throwing an exception. That error is going to occur when MVC is looking at the model for data annotations, and it will in turn ignore that attribute (since it failed to be instantiated).

By removing the static keyword, you cause the error to be thrown in your constructor when the annotation is being instantiated, so it's first thrown in your code and the debugger can break on it.