Skip to content

Commit

Permalink
Merge pull request #43 from justinsa/master
Browse files Browse the repository at this point in the history
RequiredHttpAttribute and some regular expressions
  • Loading branch information
srkirkland committed Mar 18, 2013
2 parents 8380018 + 4794169 commit b1f9af6
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 9 deletions.
Expand Up @@ -65,6 +65,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Controllers\HomeControllerTest.cs" />
<Compile Include="Doubles\HttpPostedFileBaseStub.cs" />
<Compile Include="ValidationAttributes\RequiredHttpAttributeTests.cs" />
<Compile Include="TestBase.cs" />
<Compile Include="ValidationAttributes\RegularExpressionAttributeTests.cs" />
<Compile Include="ValidationAttributes\YearAttributeTests.cs" />
Expand Down
Expand Up @@ -11,7 +11,7 @@ public void IsCuitValidTests()
{
var attribute = new RegularExpressionAttribute(Expressions.Cuit);

Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid("20245597151"));
Assert.IsTrue(attribute.IsValid("20-24559715-1"));
Assert.IsTrue(attribute.IsValid("27-23840320-6"));
Expand All @@ -24,6 +24,113 @@ public void IsCuitValidTests()
Assert.IsFalse(attribute.IsValid("4408 0412 3456 7890"));
Assert.IsFalse(attribute.IsValid(0));
Assert.IsFalse(attribute.IsValid(123));
}
}

[TestMethod]
public void IsPhoneNumberValidTests()
{
var attribute = new RegularExpressionAttribute(Expressions.PhoneNumber);

Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(string.Empty));
Assert.IsTrue(attribute.IsValid("1234567"));
Assert.IsTrue(attribute.IsValid("20 24559715 1"));
Assert.IsTrue(attribute.IsValid("123456789"));
Assert.IsTrue(attribute.IsValid("+1(234)4567789"));
Assert.IsTrue(attribute.IsValid("+235 7789"));
Assert.IsTrue(attribute.IsValid("00 27 238.40320"));
Assert.IsTrue(attribute.IsValid("0011 87 9854 23"));
Assert.IsTrue(attribute.IsValid("011 777899 854-2356"));
Assert.IsTrue(attribute.IsValid("0011 (87) 9854 23"));
Assert.IsTrue(attribute.IsValid("011 (777899) 854-2356"));
Assert.IsTrue(attribute.IsValid("07778542356"));
Assert.IsTrue(attribute.IsValid("1.234.567-0000"));
Assert.IsTrue(attribute.IsValid("1/23/56/000"));
Assert.IsTrue(attribute.IsValid("1/2/5/0"));
Assert.IsTrue(attribute.IsValid("4408 0412 3456 7890"));
Assert.IsTrue(attribute.IsValid("00"));
Assert.IsTrue(attribute.IsValid(12));
Assert.IsTrue(attribute.IsValid(123));
Assert.IsTrue(attribute.IsValid(1234567890));

Assert.IsFalse(attribute.IsValid("1/2/"));
Assert.IsFalse(attribute.IsValid("1/23/56/000-"));
Assert.IsFalse(attribute.IsValid("-1/23/56/000"));
Assert.IsFalse(attribute.IsValid("a"));
Assert.IsFalse(attribute.IsValid("0"));
Assert.IsFalse(attribute.IsValid("+0"));
Assert.IsFalse(attribute.IsValid("+00 27 238.40320"));
Assert.IsFalse(attribute.IsValid("+0011 (87) 9854 23"));
Assert.IsFalse(attribute.IsValid("+011 (777899) 854-2356"));
Assert.IsFalse(attribute.IsValid("+07778542356"));
Assert.IsFalse(attribute.IsValid(0));
}

[TestMethod]
public void IsCurrencyCodeValidTests()
{
var attribute = new RegularExpressionAttribute(Expressions.CurrencyCode);

Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(string.Empty));
Assert.IsTrue(attribute.IsValid("ABC"));
Assert.IsTrue(attribute.IsValid("XYZ"));
Assert.IsTrue(attribute.IsValid("123"));
Assert.IsTrue(attribute.IsValid("789"));

Assert.IsFalse(attribute.IsValid("AB"));
Assert.IsFalse(attribute.IsValid("WXYZ"));
Assert.IsFalse(attribute.IsValid("12"));
Assert.IsFalse(attribute.IsValid("6789"));
}

[TestMethod]
public void IsCountryCodeValidTests()
{
var attribute = new RegularExpressionAttribute(Expressions.CountryCode);

Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(string.Empty));
Assert.IsTrue(attribute.IsValid("AB"));
Assert.IsTrue(attribute.IsValid("XYZ"));
Assert.IsTrue(attribute.IsValid("123"));
Assert.IsTrue(attribute.IsValid("789"));

Assert.IsFalse(attribute.IsValid("A"));
Assert.IsFalse(attribute.IsValid("WXYZ"));
Assert.IsFalse(attribute.IsValid("12"));
Assert.IsFalse(attribute.IsValid("6789"));
}

[TestMethod]
public void IsCultureCodeValidTests()
{
var attribute = new RegularExpressionAttribute(Expressions.CultureCode);

Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(string.Empty));
Assert.IsTrue(attribute.IsValid("ab"));
Assert.IsTrue(attribute.IsValid("XYZ"));
Assert.IsTrue(attribute.IsValid("ab-WxYz"));
Assert.IsTrue(attribute.IsValid("abc-wXyZ"));
Assert.IsTrue(attribute.IsValid("ab-cd"));
Assert.IsTrue(attribute.IsValid("UVW-XYZ"));
Assert.IsTrue(attribute.IsValid("ab-123"));
Assert.IsTrue(attribute.IsValid("XYZ-789"));
Assert.IsTrue(attribute.IsValid("ab-CD-Efgh"));
Assert.IsTrue(attribute.IsValid("UVW-XYZ-abcd"));
Assert.IsTrue(attribute.IsValid("ab-123-cdef"));
Assert.IsTrue(attribute.IsValid("XYZ-789-abcd"));

Assert.IsFalse(attribute.IsValid("12"));
Assert.IsFalse(attribute.IsValid("789"));
Assert.IsFalse(attribute.IsValid("6789"));
Assert.IsFalse(attribute.IsValid("A"));
Assert.IsFalse(attribute.IsValid("AB-"));
Assert.IsFalse(attribute.IsValid("WXYZ"));
Assert.IsFalse(attribute.IsValid("AB-789-XYZ"));
Assert.IsFalse(attribute.IsValid("AB-789-VWXYZ"));
Assert.IsFalse(attribute.IsValid("UVW-XYZ-0000"));
}
}
}
@@ -0,0 +1,87 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Web;
using DataAnnotationsExtensions.ServerSideValidators;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DataAnnotationsExtensions.Tests.ValidationAttributes
{
[TestClass]
public class RequiredHttpAttributeTests
{
[TestMethod]
public void IsValidFallbackTests()
{
var attribute = new RequiredHttpAttribute();

Assert.IsTrue(attribute.IsValid("A"));
Assert.IsTrue(attribute.IsValid("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));
Assert.IsFalse(attribute.IsValid(" "));
Assert.IsFalse(attribute.IsValid(null));
Assert.IsFalse(attribute.IsValid(string.Empty));
}

[TestMethod]
public void IsValidRequiredTests()
{
var attribute = new RequiredHttpAttribute()
{
HttpMethod = "GET"
};
HttpRequest request = new HttpRequest("test", "http://localhost", string.Empty)
{
RequestType = "GET"
};
HttpResponse response = new HttpResponse(HttpWriter.Null);
HttpContext.Current = new HttpContext(request, response);

Assert.IsTrue(attribute.IsValid("A"));
Assert.IsTrue(attribute.IsValid("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));
Assert.IsFalse(attribute.IsValid(" "));
Assert.IsFalse(attribute.IsValid(null));
Assert.IsFalse(attribute.IsValid(string.Empty));
}

[TestMethod]
public void IsValidMultiMethodRequiredTests()
{
var attribute = new RequiredHttpAttribute()
{
HttpMethod = "GeT|PosT"
};
HttpRequest request = new HttpRequest("test", "http://localhost", string.Empty)
{
RequestType = "post"
};
HttpResponse response = new HttpResponse(HttpWriter.Null);
HttpContext.Current = new HttpContext(request, response);

Assert.IsTrue(attribute.IsValid("A"));
Assert.IsTrue(attribute.IsValid("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));
Assert.IsFalse(attribute.IsValid(" "));
Assert.IsFalse(attribute.IsValid(null));
Assert.IsFalse(attribute.IsValid(string.Empty));
}

[TestMethod]
public void IsValidIgnoreRequiredTests()
{
var attribute = new RequiredHttpAttribute()
{
HttpMethod = "POST"
};
HttpRequest request = new HttpRequest("test", "http://localhost", string.Empty)
{
RequestType = "Get"
};
HttpContext context = new HttpContext(request, null);

Assert.IsTrue(attribute.IsValid("A"));
Assert.IsTrue(attribute.IsValid("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));
Assert.IsTrue(attribute.IsValid(" "));
Assert.IsTrue(attribute.IsValid(null));
Assert.IsTrue(attribute.IsValid(string.Empty));
}
}
}
Expand Up @@ -16,7 +16,12 @@ public void IsValidTests()
Assert.IsTrue(attribute.IsValid(null)); // Optional values are always valid
Assert.IsTrue(attribute.IsValid("http://foo.bar"));
Assert.IsTrue(attribute.IsValid("https://foo.bar"));
Assert.IsTrue(attribute.IsValid("ftp://foo.bar"));
Assert.IsTrue(attribute.IsValid("ftp://foo.bar"));
Assert.IsTrue(attribute.IsValid("http://foo.bar:12345"));
Assert.IsTrue(attribute.IsValid("http://localhost"));
Assert.IsTrue(attribute.IsValid("http://local-host"));
Assert.IsTrue(attribute.IsValid("http://localhost:67890"));
Assert.IsTrue(attribute.IsValid("http://localhost:99999/foo.png"));
Assert.IsFalse(attribute.IsValid("file:///foo.bar"));
Assert.IsFalse(attribute.IsValid("http://user%password@foo.bar/"));
Assert.IsFalse(attribute.IsValid("foo.png"));
Expand All @@ -30,9 +35,14 @@ public void IsValidTests()
Assert.IsTrue(attribute.IsValid(null)); // Optional values are always valid
Assert.IsTrue(attribute.IsValid("foo.bar"));
Assert.IsTrue(attribute.IsValid("www.foo.bar"));
Assert.IsTrue(attribute.IsValid("http://foo.bar"));
Assert.IsFalse(attribute.IsValid("htp://foo.bar"));

Assert.IsTrue(attribute.IsValid("http://foo.bar"));
Assert.IsTrue(attribute.IsValid("http://localhost"));
Assert.IsTrue(attribute.IsValid("localhost"));
Assert.IsTrue(attribute.IsValid("localhost:12345"));
Assert.IsFalse(attribute.IsValid("htp://foo.bar"));
Assert.IsFalse(attribute.IsValid("-localhost"));
Assert.IsFalse(attribute.IsValid(".localhost"));
Assert.IsFalse(attribute.IsValid("1localhost"));
}

[TestMethod]
Expand All @@ -43,6 +53,8 @@ public void IsValidDisallowProtocol()
Assert.IsTrue(attribute.IsValid(null)); // Optional values are always valid
Assert.IsTrue(attribute.IsValid("foo.bar"));
Assert.IsTrue(attribute.IsValid("www.foo.bar"));
Assert.IsTrue(attribute.IsValid("localhost"));
Assert.IsFalse(attribute.IsValid("https://localhost"));
Assert.IsFalse(attribute.IsValid("http://foo.bar"));
Assert.IsFalse(attribute.IsValid("htp://foo.bar"));

Expand Down
1 change: 1 addition & 0 deletions DataAnnotationsExtensions/DataAnnotationsExtensions.csproj
Expand Up @@ -64,6 +64,7 @@
<Compile Include="MinAttribute.cs" />
<Compile Include="NumericAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequiredHttpAttribute.cs" />
<Compile Include="Resources\ValidatorResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
Expand Down
39 changes: 39 additions & 0 deletions DataAnnotationsExtensions/Expressions.cs
Expand Up @@ -3,5 +3,44 @@
public static class Expressions
{
public const string Cuit = @"^[0-9]{2}-?[0-9]{8}-?[0-9]$";

/// <summary>
/// Matches:
/// International dialing prefix: {{}, +, 0, 0000} (with or without a trailing break character, if not '+': [-/. ])
/// > ((\+)|(0(\d+)?[-/.\s]))
/// Country code: {{}, 1, ..., 999} (with or without a trailing break character: [-/. ])
/// > [1-9]\d{,2}[-/.\s]?
/// Area code: {(0), ..., (000000), 0, ..., 000000} (with or without a trailing break character: [-/. ])
/// > ((\(\d{1,6}\)|\d{1,6})[-/.\s]?)?
/// Local: {0, ...}+ (with or without a trailing break character: [-/. ])
/// > (\d+[-/.\s]?)+\d+
/// </summary>
/// <remarks>
/// This regular expression is not complete for identifying the numerous variations that exist in phone numbers.
/// It provides basic assertions on the format and will help to eliminate most nonsense input but does not
/// guarantee validity of the value entered for any specific geography. If greater value checking is required
/// then consider: http://nuget.org/packages/libphonenumber-csharp.
/// </remarks>
public const string PhoneNumber = @"^((\+|(0(\d+)?[-/.\s]?))[1-9]\d{0,2}[-/.\s]?)?((\(\d{1,6}\)|\d{1,6})[-/.\s]?)?(\d+[-/.\s]?)+\d+$";

/// <summary>
/// ISO 4217:2008: Currency codes. Confirms structure not validity.
/// </summary>
public const string CurrencyCode = @"^[a-zA-Z]{3}|[0-9]{3}$";

/// <summary>
/// ISO 3166: Country codes. Confirms structure not validity.
/// </summary>
public const string CountryCode = @"^[a-zA-Z]{2,3}|[0-9]{3}$";

/// <summary>
/// RFC 4646: ISO 639 + ISO 3166: Culture codes. Confirms structure not validity.
/// Matches:
/// Neutral culture: {aa, ..., ZZZ}
/// Neutral culture with script: {aa-aaaa, ..., ZZZ-ZZZZ}
/// Specific culture: {aa-aa, ..., ZZZ-ZZZ, aa-000, ... ZZZ-000}
/// Specific culture with script: {aa-aa-aaaa, ..., ZZZ-ZZZ-ZZZZ, aa-000-aaaa, ..., ZZZ-000-ZZZZ}
/// </summary>
public const string CultureCode = @"^[a-zA-Z]{2,3}(-([a-zA-Z]{2,3}|[0-9]{3}))?(-[a-zA-Z]{4})?$";
}
}
67 changes: 67 additions & 0 deletions DataAnnotationsExtensions/RequiredHttpAttribute.cs
@@ -0,0 +1,67 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.Web;

namespace DataAnnotationsExtensions.ServerSideValidators
{
/// <summary>
/// RequiredHttpAttribute class. Identical behavior to the RequiredAttribute class
/// with the additon that it allows for bypassing validation based on the Http
/// method of the current Http request.
/// </summary>
/// <remarks>
/// This class will have identical behavior to the RequiredAttribute class in the scenario
/// where the System.Web.HttpContext.Current property returns null.
/// </remarks>
[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class RequiredHttpAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
/// <summary>
/// Gets or sets the HTTP methods that this attribute will validate on.
/// </summary>
/// <remarks>The value is treated as a regular expression pattern, e.g., GET|POST.</remarks>
public string HttpMethod { get; set; }

/// <summary>
/// Validates the specified value.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <returns>True if the value is valid and false otherwise.</returns>
public override bool IsValid(object value)
{
var required = this.RequiredInContext(HttpContext.Current);
return required ? base.IsValid(value) : true;
}

/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>An instance of the ValidationResult class.</returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var required = this.RequiredInContext(HttpContext.Current);
return required ? base.IsValid(value, validationContext) : ValidationResult.Success;
}

/// <summary>
/// Determines if the provided HTTP context permits validation.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>True if validation is required and false otherwise.</returns>
protected bool RequiredInContext(HttpContext context)
{
if (context != null && !string.IsNullOrWhiteSpace(this.HttpMethod))
{
if (!Regex.IsMatch(context.Request.HttpMethod, this.HttpMethod, RegexOptions.IgnoreCase))
{
return false;
}
}

return true;
}
}
}

0 comments on commit b1f9af6

Please sign in to comment.