Open
Description
Background and motivation
It would be useful to supply a Regex object to a RegularExpressionAttribute so that you can use a compile-time Regex for data validation.
API Proposal
+ using System.Diagnostics.CodeAnalysis;
namespace System.ComponentModel.DataAnnotations;
public class RegularExpressionAttribute : ValidationAttribute
{
/// <summary>
/// Constructor that accepts the regular expression pattern
/// </summary>
/// <param name="pattern">The regular expression to use. It cannot be null.</param>
public RegularExpressionAttribute([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Regex")] string pattern) { }
+ /// <summary>
+ /// Create a <see cref="RegularExpressionAttribute"/> using a <see cref="Regex"/> returned from the specified type and method name.
+ /// </summary>
+ /// <param name="regexType">The type that contains the method returning a <see cref="Regex"/>.</param>
+ /// <param name="regexMethodName">The method name that returns the <see cref="Regex"/>. The method must be static and accept no arguments.</param>
+ /// <exception cref="ArgumentNullException">When the <paramref name="regexType"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException">When the <paramref name="regexMethodName"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentException">When the <paramref name="regexMethodName"/> is empty or consists only of white-space characters.</exception>
+ public RegularExpressionAttribute([DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.AllMethods)] System.Type regexType, string regexMethodName) { }
}
API Usage
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
var model = new Model { MustTypeAgree = "agree" };
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model), results, true);
Console.WriteLine($"MustTypeAgree: {model.MustTypeAgree}");
Console.WriteLine($"IsValid: {isValid}");
// MustTypeAgree: agree
// IsValid: true
public partial class Model
{
[Required]
[RegularExpression(typeof(Model), "GetAgreeRegex")]
public string? MustTypeAgree { get; set; }
[GeneratedRegex("AGREE", RegexOptions.IgnoreCase)]
public static partial Regex GetAgreeRegex();
}
Alternative Designs
- Skip integrating
[GeneratedRegex]
withRegularExpressionAttribute
and instead use a source generator to create custom regex validation attribute class. - Implement a separate
ValidationAttribute
or perhaps derive fromRegularExpressionAttribute
to handle this scenario separately. - Instead of adding a constructor overload, add a
Type? RegexType { get; init; }
property that would pair with the existingPattern
such that when it's set, we would enter into the new behavior.
Risks
- For sake of design-time scenarios, we try to avoid throwing exceptions (other than argument validation exceptions) from attribute constructors. With this in mind, we need defensive code in the new attribute initialization.
- Any validation systems that have special awareness of
RegularExpressionAttribute
to perform special-purpose logic might not take advantage of the new behavior and end up reconstructing theRegex
instance from only the pattern, ignoring anyRegexOptions
specified on the[GeneratedRegex]
.- For instance, Swashbuckle.AspNetCore extracts the
Pattern
from the attribute and embeds it into the OpenApiSchema. This will not respectRegexOptions.IgnoreCase
or other options.
- For instance, Swashbuckle.AspNetCore extracts the
GeneratedRegexAttribute
applies a default timeout ofTimeout.Infinite
whileRegularExpressionAttribute
applies a default of 2000ms. While we should likely respect a non-infinite timeout specified on[GeneratedRegex]
, we need to make sure the 2000ms default for the attribute is still applied instead ofTimeout.Infinite
.
Original Proposal
API Proposal
namespace System.ComponentModel.DataAnnotations;
public class RegularExpressionAttribute : ValidationAttribute
{
/// <summary>
/// Initializes a new instance of the System.ComponentModel.DataAnnotations.RegularExpressionAttribute class.
/// </summary>
/// <param name="pattern">The <see cref="Regex"/> that is used to validate the data field value.</param>
/// <exception cref="System.ArgumentNullException">pattern is null</exception>
public RegularExpressionAttribute(Regex pattern);
}
API Usage
public partial class CannedRegularExpressionAttribute() : RegularExpressionAttribute(TheRegex())
{
[GeneratedRegex(@"<Insert some complicated regular expression here>")]
private static partial Regex TheRegex();
}
public partial record ARecord {
[RegularExpressionAttribute(TheRegex())]
public string NeedsComplexValidation;
[GeneratedRegex(@"<Insert some complicated regular expression here>")]
private static partial Regex TheRegex();
}
Alternative Designs
Perhaps there would be extra performance gains from generating a RegularExpressionAttribute's Validation method directly?
Risks
No API (that I can spot) on Regex allows specifying matchTimeout for an existing Regex object.