diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs new file mode 100644 index 0000000000..e7d92ef13c --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. 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.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Routing.Constraints +{ + /// + /// Constrains a route parameter to contain only a specified strign. + /// + public class StringRouteConstraint : IRouteConstraint + { + private string _value; + + /// + /// Initializes a new instance of the class. + /// + /// The constraint value to match. + public StringRouteConstraint(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _value = value; + } + + /// + public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } + + if (routeKey == null) + { + throw new ArgumentNullException(nameof(routeKey)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + object routeValue; + + if (values.TryGetValue(routeKey, out routeValue) + && routeValue != null) + { + var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture); + + return parameterValueString.Equals(_value, StringComparison.OrdinalIgnoreCase); + } + + return false; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Constraints/StringRouteConstraintTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Constraints/StringRouteConstraintTest.cs new file mode 100644 index 0000000000..006ce0d382 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Constraints/StringRouteConstraintTest.cs @@ -0,0 +1,157 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Routing.Constraints +{ + public class StringRouteConstraintTest + { + [Fact] + public void StringRouteConstraintSimpleTrueWithRouteDirectionIncomingRequestTest() + { + // Arrange + var constraint = new StringRouteConstraint("home"); + + // Act + var values = new RouteValueDictionary(new { controller = "home" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "controller", + values: values, + routeDirection: RouteDirection.IncomingRequest); + + // Assert + Assert.True(match); + } + + [Fact] + public void StringRouteConstraintSimpleTrueWithRouteDirectionUrlGenerationTest() + { + // Arrange + var constraint = new StringRouteConstraint("home"); + + // Act + var values = new RouteValueDictionary(new { controller = "home" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "controller", + values: values, + routeDirection: RouteDirection.UrlGeneration); + + // Assert + Assert.True(match); + } + + [Fact] + public void StringRouteConstraintSimpleFalseWithRouteDirectionIncomingRequestTest() + { + // Arrange + var constraint = new StringRouteConstraint("admin"); + + // Act + var values = new RouteValueDictionary(new { controller = "home" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "controller", + values: values, + routeDirection: RouteDirection.IncomingRequest); + + // Assert + Assert.False(match); + } + + [Fact] + public void StringRouteConstraintSimpleFalseWithRouteDirectionUrlGenerationTest() + { + // Arrange + var constraint = new StringRouteConstraint("admin"); + + // Act + var values = new RouteValueDictionary(new { controller = "home" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "controller", + values: values, + routeDirection: RouteDirection.UrlGeneration); + + // Assert + Assert.False(match); + } + + [Fact] + public void StringRouteConstraintKeyNotFoundWithRouteDirectionIncomingRequestTest() + { + // Arrange + var constraint = new StringRouteConstraint("admin"); + + // Act + var values = new RouteValueDictionary(new { controller = "admin" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "action", + values: values, + routeDirection: RouteDirection.IncomingRequest); + + // Assert + Assert.False(match); + } + + [Fact] + public void StringRouteConstraintKeyNotFoundWithRouteDirectionUrlGenerationTest() + { + // Arrange + var constraint = new StringRouteConstraint("admin"); + + // Act + var values = new RouteValueDictionary(new { controller = "admin" }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "action", + values: values, + routeDirection: RouteDirection.UrlGeneration); + + // Assert + Assert.False(match); + } + + [Theory] + [InlineData("User", "uSer", true)] + [InlineData("User.Admin", "User.Admin", true)] + [InlineData(@"User\Admin", "User\\Admin", true)] + [InlineData(null, "user", false)] + public void StringRouteConstraintEscapingCaseSensitiveAndRouteNullTest(string routeValue, string constraintValue, bool expected) + { + // Arrange + var constraint = new StringRouteConstraint(constraintValue); + + // Act + var values = new RouteValueDictionary(new { controller = routeValue }); + + var match = constraint.Match( + httpContext: Mock.Of(), + route: new Mock().Object, + routeKey: "controller", + values: values, + routeDirection: RouteDirection.IncomingRequest); + + // Assert + Assert.Equal(expected, match); + } + } +} \ No newline at end of file