From 21031a3aa82ac6618da6320701be7ea0af0fdf34 Mon Sep 17 00:00:00 2001 From: ianhong Date: Thu, 9 Apr 2015 10:53:39 -0700 Subject: [PATCH] Add sample and functional test for custom inline constraints --- .../InlineConstraintTests.cs | 64 ++++++---- .../IsbnDigitScheme10Constraint.cs | 115 ++++++++++++++++++ .../IsbnDigitScheme13Constraint.cs | 58 +++++++++ .../InlineConstraints_Isbn10Controller.cs | 13 ++ .../InlineConstraints_Isbn13Controller.cs | 15 +++ .../InlineConstraintsWebSite/Startup.cs | 27 ++++ 6 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme10Constraint.cs create mode 100644 test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme13Constraint.cs create mode 100644 test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn10Controller.cs create mode 100644 test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn13Controller.cs diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs index 81f0ae2377..730698dbe4 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs @@ -158,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); // Act - var response = + var response = await client.GetAsync(@"http://localhost/products/GetProductByManufacturingDate/2014-10-11T13:45:30"); // Assert @@ -176,7 +176,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var client = server.CreateClient(); - + // Act var response = await client.GetAsync("http://localhost/products/GetProductByCategoryName/Sports"); @@ -196,7 +196,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); // Act - var response = + var response = await client.GetAsync("http://localhost/products/GetProductByCategoryName/SportsSportsSportsSports"); // Assert @@ -226,7 +226,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var client = server.CreateClient(); - + // Act var response = await client.GetAsync("http://localhost/products/GetProductByCategoryId/40"); @@ -478,10 +478,30 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + [Theory] + // Testing custom inline constraint updated in ConstraintMap + [InlineData("1234567890128", "13 Digit ISBN Number")] + // Testing custom inline constraint configured via MapRoute + [InlineData("1-234-56789-X", "10 Digit ISBN Number")] + public async Task CustomInlineConstraint_Add_Update(string isbn, string expectedBody) + { + // Arrange + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/book/index/" + isbn); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains(expectedBody, body); + } + public static IEnumerable QueryParameters { // The first four parameters are controller name, action name, parameters in the query and their values. - // These are used to generate a link, the last parameter is expected generated link + // These are used to generate a link, the last parameter is expected generated link get { // Attribute Route, id:int? constraint @@ -495,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, id:int? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductById", @@ -514,7 +534,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, name:length(1,20)? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByCategoryName", @@ -524,7 +544,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, name:length(1,20)? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByCategoryName", @@ -534,7 +554,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, catId:int:range(10, 100) constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByCategoryId", @@ -544,7 +564,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, catId:int:range(10, 100) constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByCategoryId", @@ -554,7 +574,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, name:length(1,20)? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByPrice", @@ -564,7 +584,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, price:float? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByManufacturerId", @@ -574,7 +594,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, manId:int:min(10)? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByManufacturerId", @@ -584,7 +604,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, manId:int:min(10)? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByManufacturerId", @@ -594,7 +614,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Attribute Route, dateTime:datetime constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Products", "GetProductByManufacturingDate", @@ -604,7 +624,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests }; // Conventional Route, id:guid? constraint - yield return new object[] + yield return new object[] { "InlineConstraints_Store", "GetStoreById", @@ -618,10 +638,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests [Theory] [MemberData(nameof(QueryParameters))] public async Task GetGeneratedLink( - string controller, - string action, - string parameterName, - string parameterValue, + string controller, + string action, + string parameterName, + string parameterValue, string expectedLink) { // Arrange @@ -647,12 +667,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests controller, action, parameterName, - parameterValue); + parameterValue); } var response = await client.GetAsync(url); - // Assert + // Assert var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedLink, body); } diff --git a/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme10Constraint.cs b/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme10Constraint.cs new file mode 100644 index 0000000000..8fe495e68a --- /dev/null +++ b/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme10Constraint.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing; + +namespace InlineConstraintsWebSite.Constraints +{ + public class IsbnDigitScheme10Constraint : IRouteConstraint + { + private readonly bool _allowDashes; + + public IsbnDigitScheme10Constraint(bool allowDashes) + { + _allowDashes = allowDashes; + } + + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + IDictionary values, + RouteDirection routeDirection) + { + object value; + + if (!values.TryGetValue(routeKey, out value)) + { + return false; + } + + var inputString = value as string; + string isbnNumber; + + if (inputString == null + || !TryGetIsbn10(inputString, _allowDashes, out isbnNumber)) + { + return false; + } + + var sum = 0; + Func convertToInt = (char n) => n - '0'; + + for (int i = 0; i < isbnNumber.Length - 1; ++i) + { + sum += convertToInt(isbnNumber[i]) * (i + 1); + } + + var checkSum = sum % 11; + var lastDigit = isbnNumber.Last(); + + if (checkSum == 10) + { + return char.ToUpperInvariant(lastDigit) == 'X'; + } + else + { + return checkSum == convertToInt(lastDigit); + } + } + + private static bool TryGetIsbn10(string value, bool allowDashes, out string isbnNumber) + { + if (!allowDashes) + { + if (CheckIsbn10Characters(value)) + { + isbnNumber = value; + return true; + } + else + { + isbnNumber = null; + return false; + } + } + + var isbnParts = value.Split( + new char[] { '-' }, + StringSplitOptions.RemoveEmptyEntries); + + if (isbnParts.Length == 4) + { + value = value.Replace("-", string.Empty); + if (CheckIsbn10Characters(value)) + { + isbnNumber = value; + return true; + } + } + + isbnNumber = null; + return false; + } + + private static bool CheckIsbn10Characters(string value) + { + if (value.Length != 10) + { + return false; + } + + var digits = value.Substring(0, 9); + var checksum = value.Last(); + + return digits.All(n => '0' <= n && n <= '9') + && ('0' <= checksum && checksum <= '9' + || 'X' == char.ToUpperInvariant(checksum)); + } + } +} \ No newline at end of file diff --git a/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme13Constraint.cs b/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme13Constraint.cs new file mode 100644 index 0000000000..49fbfb96cb --- /dev/null +++ b/test/WebSites/InlineConstraintsWebSite/Constraints/IsbnDigitScheme13Constraint.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing; + +namespace InlineConstraintsWebSite.Constraints +{ + public class IsbnDigitScheme13Constraint : IRouteConstraint + { + private static readonly int[] _isbn13Weights = new int[] { 1, 3 }; + + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + IDictionary values, + RouteDirection routeDirection) + { + object value; + + if (!values.TryGetValue(routeKey, out value)) + { + return false; + } + + var isbnNumber = value as string; + + if (isbnNumber == null + || isbnNumber.Length != 13 + || isbnNumber.Any(n => n < '0' || n > '9')) + { + return false; + } + + var sum = 0; + Func convertToInt = (char n) => n - '0'; + + for (int i = 0; i < isbnNumber.Length - 1; ++i) + { + sum += + convertToInt(isbnNumber[i]) * _isbn13Weights[i % 2]; + } + + var checkSum = 10 - sum % 10; + + if (checkSum == convertToInt(isbnNumber.Last())) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn10Controller.cs b/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn10Controller.cs new file mode 100644 index 0000000000..0b983f8e87 --- /dev/null +++ b/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn10Controller.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNet.Mvc; +using System; + +namespace InlineConstraintsWebSite.Controllers +{ + public class InlineConstraints_Isbn10Controller : Controller + { + public string Index(string isbnNumber) + { + return "10 Digit ISBN Number " + isbnNumber; + } + } +} \ No newline at end of file diff --git a/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn13Controller.cs b/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn13Controller.cs new file mode 100644 index 0000000000..ade961990d --- /dev/null +++ b/test/WebSites/InlineConstraintsWebSite/Controllers/InlineConstraints_Isbn13Controller.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNet.Mvc; +using System; + +namespace InlineConstraintsWebSite.Controllers +{ + [Route("book/[action]")] + public class InlineConstraints_Isbn13Controller : Controller + { + [HttpGet("{isbnNumber:IsbnDigitScheme13}")] + public string Index(string isbnNumber) + { + return "13 Digit ISBN Number " + isbnNumber; + } + } +} \ No newline at end of file diff --git a/test/WebSites/InlineConstraintsWebSite/Startup.cs b/test/WebSites/InlineConstraintsWebSite/Startup.cs index d7da3d4054..9a70aee707 100644 --- a/test/WebSites/InlineConstraintsWebSite/Startup.cs +++ b/test/WebSites/InlineConstraintsWebSite/Startup.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using InlineConstraintsWebSite.Constraints; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Routing.Constraints; using Microsoft.Framework.DependencyInjection; @@ -12,6 +13,27 @@ namespace InlineConstraints // Set up application services public void ConfigureServices(IServiceCollection services) { + services.ConfigureRouting( + routeOptions => routeOptions.ConstraintMap.Add( + "IsbnDigitScheme10", + typeof(IsbnDigitScheme10Constraint))); + + services.ConfigureRouting( + routeOptions => routeOptions.ConstraintMap.Add( + "IsbnDigitScheme13", + typeof(IsbnDigitScheme10Constraint))); + + // Update an existing constraint from ConstraintMap for test purpose. + services.ConfigureRouting( + routeOptions => + { + if (routeOptions.ConstraintMap.ContainsKey("IsbnDigitScheme13")) + { + routeOptions.ConstraintMap["IsbnDigitScheme13"] = + typeof(IsbnDigitScheme13Constraint); + } + }); + // Add MVC services to the services container services.AddMvc(); } @@ -24,6 +46,11 @@ namespace InlineConstraints app.UseMvc(routes => { + routes.MapRoute( + name: "isbn10", + template: "book/{action}/{isbnNumber:IsbnDigitScheme10(true)}", + defaults: new { controller = "InlineConstraints_Isbn10" }); + routes.MapRoute("StoreId", "store/{action}/{id:guid?}", defaults: new { controller = "InlineConstraints_Store" });