1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
using System.ComponentModel.DataAnnotations.Resources;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
namespace System.ComponentModel.DataAnnotations {
/// <summary>
/// Regular expression validation attribute
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
public class RegularExpressionAttribute : ValidationAttribute {
/// <summary>
/// Gets the regular expression pattern to use
/// </summary>
public string Pattern { get; private set; }
/// <summary>
/// Gets or sets the timeout to use when matching the regular expression pattern (in milliseconds)
/// (-1 means never timeout).
/// </summary>
public int MatchTimeoutInMilliseconds {
get {
return _matchTimeoutInMilliseconds;
}
set {
_matchTimeoutInMilliseconds = value;
_matchTimeoutSet = true;
}
}
private int _matchTimeoutInMilliseconds;
private bool _matchTimeoutSet;
private Regex Regex { get; set; }
/// <summary>
/// Constructor that accepts the regular expression pattern
/// </summary>
/// <param name="pattern">The regular expression to use. It cannot be null.</param>
public RegularExpressionAttribute(string pattern)
: base(() => DataAnnotationsResources.RegexAttribute_ValidationError) {
this.Pattern = pattern;
}
/// <summary>
/// Override of <see cref="ValidationAttribute.IsValid(object)"/>
/// </summary>
/// <remarks>This override performs the specific regular expression matching of the given <paramref name="value"/></remarks>
/// <param name="value">The value to test for validity.</param>
/// <returns><c>true</c> if the given value matches the current regular expression pattern</returns>
/// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
/// <exception cref="ArgumentException"> is thrown if the <see cref="Pattern"/> is not a valid regular expression.</exception>
#if !SILVERLIGHT
public
#else
internal
#endif
override bool IsValid(object value) {
this.SetupRegex();
// Convert the value to a string
string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
// Automatically pass if value is null or empty. RequiredAttribute should be used to assert a value is not empty.
if (String.IsNullOrEmpty(stringValue)) {
return true;
}
Match m = this.Regex.Match(stringValue);
// We are looking for an exact match, not just a search hit. This matches what
// the RegularExpressionValidator control does
return (m.Success && m.Index == 0 && m.Length == stringValue.Length);
}
/// <summary>
/// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
/// </summary>
/// <remarks>This override provide a formatted error message describing the pattern</remarks>
/// <param name="name">The user-visible name to include in the formatted message.</param>
/// <returns>The localized message to present to the user</returns>
/// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
/// <exception cref="ArgumentException"> is thrown if the <see cref="Pattern"/> is not a valid regular expression.</exception>
public override string FormatErrorMessage(string name) {
this.SetupRegex();
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, this.Pattern);
}
/// <summary>
/// Sets up the <see cref="Regex"/> property from the <see cref="Pattern"/> property.
/// </summary>
/// <exception cref="ArgumentException"> is thrown if the current <see cref="Pattern"/> cannot be parsed</exception>
/// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
/// <exception cref="ArgumentOutOfRangeException"> thrown if <see cref="MatchTimeoutInMilliseconds" /> is negative (except -1),
/// zero or greater than approximately 24 days </exception>
private void SetupRegex() {
if (this.Regex == null) {
if (string.IsNullOrEmpty(this.Pattern)) {
throw new InvalidOperationException(DataAnnotationsResources.RegularExpressionAttribute_Empty_Pattern);
}
if (!_matchTimeoutSet) {
MatchTimeoutInMilliseconds = GetDefaultTimeout();
}
Regex = MatchTimeoutInMilliseconds == -1
? new Regex(Pattern)
: Regex = new Regex(Pattern, default(RegexOptions), TimeSpan.FromMilliseconds((double)MatchTimeoutInMilliseconds));
}
}
/// <summary>
/// Returns the default MatchTimeout based on UseLegacyRegExTimeout switch.
/// </summary>
private static int GetDefaultTimeout() {
#if !MONO
if (LocalAppContextSwitches.UseLegacyRegExTimeout) {
return -1;
}
else
#endif
{
return 2000;
}
}
}
}
|