From 27d6a735af770c8b7650c07640b754dabeaba036 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 2 Jul 2014 16:38:30 -0700 Subject: [PATCH] Implement a 'required' constraint This is useful for a variety of interesting scenarios in link generation where a default value doesn't appear in the route template as a parameter. This can be used to implement the desired behavior for areas - where the 'area' key is sticky. --- .../RoutingSample.Web/RoutingSample.Web.kproj | 3 + .../Constraints/RequiredRouteConstraint.cs | 39 ++++++++ .../Microsoft.AspNet.Routing.kproj | 1 + .../RequiredRouteConstraintTests.cs | 98 +++++++++++++++++++ .../Microsoft.AspNet.Routing.Tests.kproj | 1 + 5 files changed, 142 insertions(+) create mode 100644 src/Microsoft.AspNet.Routing/Constraints/RequiredRouteConstraint.cs create mode 100644 test/Microsoft.AspNet.Routing.Tests/Constraints/RequiredRouteConstraintTests.cs diff --git a/samples/RoutingSample.Web/RoutingSample.Web.kproj b/samples/RoutingSample.Web/RoutingSample.Web.kproj index 7d2e68bedd..733bc0bafd 100644 --- a/samples/RoutingSample.Web/RoutingSample.Web.kproj +++ b/samples/RoutingSample.Web/RoutingSample.Web.kproj @@ -20,6 +20,9 @@ 28778 + + 22209 + diff --git a/src/Microsoft.AspNet.Routing/Constraints/RequiredRouteConstraint.cs b/src/Microsoft.AspNet.Routing/Constraints/RequiredRouteConstraint.cs new file mode 100644 index 0000000000..899470b4dc --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Constraints/RequiredRouteConstraint.cs @@ -0,0 +1,39 @@ +// 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.Globalization; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Routing.Constraints +{ + /// + /// Constraints a route parameter that must have a value. + /// + /// + /// This constraint is primarily used to enforce that a non-parameter value is present during + /// URL generation. + /// + public class RequiredRouteConstraint : IRouteConstraint + { + /// + public bool Match( + [NotNull]HttpContext httpContext, + [NotNull]IRouter route, + [NotNull]string routeKey, + [NotNull]IDictionary values, + RouteDirection routeDirection) + { + object value; + if (values.TryGetValue(routeKey, out value) && value != null) + { + // In routing the empty string is equivalent to null, which is equivalent to an unset value. + var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); + return !string.IsNullOrEmpty(valueString); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj b/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj index 8b6727ba9a..6b0e870438 100644 --- a/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj +++ b/src/Microsoft.AspNet.Routing/Microsoft.AspNet.Routing.kproj @@ -41,6 +41,7 @@ + diff --git a/test/Microsoft.AspNet.Routing.Tests/Constraints/RequiredRouteConstraintTests.cs b/test/Microsoft.AspNet.Routing.Tests/Constraints/RequiredRouteConstraintTests.cs new file mode 100644 index 0000000000..818cb2f564 --- /dev/null +++ b/test/Microsoft.AspNet.Routing.Tests/Constraints/RequiredRouteConstraintTests.cs @@ -0,0 +1,98 @@ +// 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. + +#if NET45 + +using System; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing.Constraints; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Routing.Tests +{ + public class RequiredRouteConstraintTests + { + [Theory] + [InlineData(RouteDirection.IncomingRequest)] + [InlineData(RouteDirection.UrlGeneration)] + public void RequiredRouteConstraint_NoValue(RouteDirection direction) + { + // Arrange + var constraint = new RequiredRouteConstraint(); + + // Act + var result = constraint.Match( + Mock.Of(), + Mock.Of(), + "area", + new RouteValueDictionary(new { controller = "Home", action = "Index" }), + direction); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(RouteDirection.IncomingRequest)] + [InlineData(RouteDirection.UrlGeneration)] + public void RequiredRouteConstraint_Null(RouteDirection direction) + { + // Arrange + var constraint = new RequiredRouteConstraint(); + + // Act + var result = constraint.Match( + Mock.Of(), + Mock.Of(), + "area", + new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null }), + direction); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(RouteDirection.IncomingRequest)] + [InlineData(RouteDirection.UrlGeneration)] + public void RequiredRouteConstraint_EmptyString(RouteDirection direction) + { + // Arrange + var constraint = new RequiredRouteConstraint(); + + // Act + var result = constraint.Match( + Mock.Of(), + Mock.Of(), + "area", + new RouteValueDictionary(new { controller = "Home", action = "Index", area = string.Empty}), + direction); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(RouteDirection.IncomingRequest)] + [InlineData(RouteDirection.UrlGeneration)] + public void RequiredRouteConstraint_WithValue(RouteDirection direction) + { + // Arrange + var constraint = new RequiredRouteConstraint(); + + // Act + var result = constraint.Match( + Mock.Of(), + Mock.Of(), + "area", + new RouteValueDictionary(new { controller = "Home", action = "Index", area = "Store" }), + direction); + + // Assert + Assert.True(result); + } + } +} + +#endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Routing.Tests/Microsoft.AspNet.Routing.Tests.kproj b/test/Microsoft.AspNet.Routing.Tests/Microsoft.AspNet.Routing.Tests.kproj index 04c3acab62..2d852ad36b 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Microsoft.AspNet.Routing.Tests.kproj +++ b/test/Microsoft.AspNet.Routing.Tests/Microsoft.AspNet.Routing.Tests.kproj @@ -40,6 +40,7 @@ +