Search code examples
c#asp.netregexasp.net-corevalidation

ASP.NET Core [RegularExpression] validation fails, even though the regex is demonstrably correct


I have the following method:

public async Task<IActionResult> GetUsersAPICall([Bind(nameof(query))] [RegularExpression(@"^\w+(\.\w+)*@\w+(\.\w+)+|(\w+(\s\w+)*)|[a-z0-9]+$", ErrorMessage = "This does not look like a valid query")] string query)
{
    if (!ModelState.IsValid)
    {
       return BadRequest();
    }
}

This fails for email addresses like test-def.ghi@de.jkl.com (but not for email addresses like test@test.com).

I know for a fact that the regex is the correct pattern, because the following LINQPad script displays True as expected for the exact same email address:

void Main()
{
    var r = new Regex(@"^\w+(\.\w+)*@\w+(\.\w+)+|(\w+(\s\w+)*)|[a-z0-9]+$");
    
    r.IsMatch("test-def.ghi@de.jkl.com").Dump();
}

I tried dropping the [Bind(nameof(query))] entirely, but that didn't help.

If I change this to

public async Task<IActionResult> GetUsersAPICall([Bind(nameof(query))] string query)
{
    if (!Regex.IsMatch(query, @"^\w+(\.\w+)*@\w+(\.\w+)+|(\w+(\s\w+)*)|[a-z0-9]+$"))
    {
        return BadRequest();
    }

    // ...
}

I'm completely baffled as to why what I had originally wasn't working. Can someone see what I'm missing here?

The closest I've found so far on Stack Overflow is this Q&A, but there it turned out that the OP's regex was wrong (which isn't the case for me, since the Regex works just fine in two different contexts).


Solution

  • Problem is within your regex and difference in its handling by IsMatch and RegularExpression annotation.

    IsMatch checks if input contains anything matching provided regex. Documentation:

    Indicates whether the specified regular expression finds a match in the specified input string, using the specified matching options and time-out interval.

    So it's trying to find any match in your input, finds it (specifically for second alteration: (\w+(\s\w+)*)) and returns true.

    RegularExpression annotation on the other hand checks if your whole input matches provided regex. Documentation:

    The regular expression searches for an exact match, not using ^ before and $ at the end of the pattern produces the same results as using it. For a search hit, prepend and append the pattern with .*.

    So effectively, annotation applies pattern ^(?:^\w+(\.\w+)*@\w+(\.\w+)+|(\w+(\s\w+)*)|[a-z0-9]+$)$.

    Probably you tried to achieve similar effect, but forgot parenthesis?