27

I have a string that I use for client side validation:

private const String regex = @"^(?:\b(?:\d{5}(?:\s*-\s*\d{5})?|([A-Z]{2})\d{3}(?:\s*-\s*\1\d{3})?)(?:,\s*)?)+$";

I use this string in my [RegularExpression(regex, ErrorMessage = "invalid")] attribute.

I know that the /i flag for a Javascript regex is used to make it case insensitive, but just tacking it on to the end of my regex (i.e. @"^....$/i" isn't working - the regex validation fails completely, regardless of what is entered (valid or not).

What am I missing?

Scott Baker
  • 10,013
  • 17
  • 56
  • 102
  • 1
    The problem is that .NET and JS use different flavors of Regex, so if we want the same pattern to work universally on the client and the server, we have to **use a subset of the regular expression syntax that is safe in JS and C#**. Alternatively, approaches like [Jeremy's solution below](https://stackoverflow.com/a/14962296/1366033) allows us to richly compose and control modifiers in both languages by branching the validation logic on the server and the client – KyleMit Jun 04 '18 at 19:57

3 Answers3

44

I created this attribute which allows you to specify RegexOptions. EDIT: It also integrates with unobtrusive validation. The client will only obey RegexOptions.Multiline and RegexOptions.IgnoreCase since that is what JavaScript supports.

[RegularExpressionWithOptions(@".+@example\.com", RegexOptions = RegexOptions.IgnoreCase)]

C#

public class RegularExpressionWithOptionsAttribute : RegularExpressionAttribute, IClientValidatable
{
    public RegularExpressionWithOptionsAttribute(string pattern) : base(pattern) { }

    public RegexOptions RegexOptions { get; set; }

    public override bool IsValid(object value)
    {
        if (string.IsNullOrEmpty(value as string))
            return true;

        return Regex.IsMatch(value as string, "^" + Pattern + "$", RegexOptions);
    }

    public IEnumerable<System.Web.Mvc.ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.DisplayName),
            ValidationType = "regexwithoptions"
        };

        rule.ValidationParameters["pattern"] = Pattern;

        string flags = "";
        if ((RegexOptions & RegexOptions.Multiline) == RegexOptions.Multiline)
            flags += "m";
        if ((RegexOptions & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase)
            flags += "i";
        rule.ValidationParameters["flags"] = flags;

        yield return rule;
    }
}

JavaScript

(function ($) {

    $.validator.unobtrusive.adapters.add("regexwithoptions", ["pattern", "flags"], function (options) {
        options.messages['regexwithoptions'] = options.message;
        options.rules['regexwithoptions'] = options.params;
    });

    $.validator.addMethod("regexwithoptions", function (value, element, params) {
        var match;
        if (this.optional(element)) {
            return true;
        }

        var reg = new RegExp(params.pattern, params.flags);
        match = reg.exec(value);
        return (match && (match.index === 0) && (match[0].length === value.length));
    });

})(jQuery);

This article by Anthony Stevens helped me get this working: ASP.NET MVC 3 Unobtrusive Javascript Validation With Custom Validators

Jeremy Cook
  • 20,840
  • 9
  • 71
  • 77
  • I haven't tested your solution but I know the general approach works as it solved a similar problem here: http://stackoverflow.com/questions/5937174/how-can-i-ignore-case-in-a-regularexpression – Sean Apr 10 '13 at 21:57
  • Awesome solution! I tried to clean up and leverage the out of the box regex validator by wrapping the output pattern in the [`/pattern/flag` literal syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Syntax), but the value is passed into [``new RegExp(params)`](https://github.com/aspnet/jquery-validation-unobtrusive/blob/v3.2.10/src/jquery.validate.unobtrusive.js#L345) constructor syntax which won't parse the attributes, so the approach is the right one to write the client side flag handling ourselves as well – KyleMit Jun 04 '18 at 19:56
19

In C# you can inline some regex options. To specify the option to ignore case you would add (?i) to the beginning of your pattern. However, I am not sure how this would be treated by the RegularExpressionAttribute and if it handles translation for client-side. From my experience with ASP.NET's RegularExpressionValidator I doubt it; the regex should be vanilla enough to work for both engines.

In any case if it was valid it would look like this:

@"^(?i)(?:\b(?:\d{5}(?:\s*-\s*\d{5})?|([A-Z]{2})\d{3}(?:\s*-\s*\1\d{3})?)(?:,\s*)?)+$"
Ahmad Mageed
  • 94,561
  • 19
  • 163
  • 174
7
private const String regex = @"^(?:\b(?:\d{5}(?:\s*-\s*\d{5})?|([a-zA-Z]{2})\d{3}(?:\s*-\s*\1\d{3})?)(?:,\s*)?)+$";
Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
Eric K Yung
  • 1,754
  • 11
  • 10
  • 4
    ah! of course! excellent answer - however, I must ask: is there a way to use the case insensitive flag? – Scott Baker Nov 18 '10 at 19:56
  • 1
    Regex re = new Regex(@"^(?:\b(?:\d{5}(?:\s*-\s*\d{5})?|([a-zA-Z]{2})\d{3}(?:\s*-\s*\1\d{3})?)(?:,\s*)?)+$", RegexOptions.IgnoreCase); // That's c# code. – Eric K Yung Nov 18 '10 at 20:07
  • 5
    the question is related to RegularExpressionAttribute and there you have to pass only string. So you cannot use Regex class with attributes – Lukas K Jan 30 '18 at 14:29