From 232b27ad5d3b0eca6f4e04b1b7645855c4461a36 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Tue, 15 Dec 2015 12:56:09 -0800 Subject: [PATCH] [Fixes #3430] Removed RouteKeyHandling.CatchAll --- Mvc.NoFun.sln | 17 +++- Mvc.sln | 17 +++- .../ActionConstraintSample.Web.xproj | 25 ++++++ .../Controllers/ItemsController.cs | 15 ++++ .../Controllers/US/ItemsController.cs | 16 ++++ .../CountrySpecificAttribute.cs | 35 ++++++++ samples/ActionConstraintSample.Web/Startup.cs | 29 +++++++ .../ActionConstraintSample.Web/project.json | 15 ++++ samples/ActionConstraintSample.Web/readme.md | 4 + .../wwwroot/web.config | 9 ++ .../Routing/RouteDataActionConstraint.cs | 14 --- .../Routing/RouteKeyHandling.cs | 7 -- .../ControllerActionDescriptorBuilder.cs | 30 ++----- .../RouteConstraintAttribute.cs | 22 +---- .../Properties/Resources.Designer.cs | 16 ---- src/Microsoft.AspNet.Mvc.Core/Resources.resx | 3 - .../Routing/ActionSelectionDecisionTree.cs | 83 ++---------------- .../RazorViewEngine.cs | 2 +- .../DefaultActionSelectorTests.cs | 86 ------------------- .../RouteDataActionConstraintTest.cs | 10 --- .../ActionConstraintSampleTest.cs | 52 +++++++++++ .../RoutingTests.cs | 84 ------------------ .../project.json | 1 + .../RazorViewEngineTest.cs | 28 ------ .../Products/ProductsController.cs | 23 ----- .../Products/US/ProductsController.cs | 23 ----- .../RoutingWebSite/CountryNeutralAttribute.cs | 16 ---- .../CountrySpecificAttribute.cs | 15 ---- test/WebSites/RoutingWebSite/Startup.cs | 5 -- 29 files changed, 249 insertions(+), 453 deletions(-) create mode 100644 samples/ActionConstraintSample.Web/ActionConstraintSample.Web.xproj create mode 100644 samples/ActionConstraintSample.Web/Controllers/ItemsController.cs create mode 100644 samples/ActionConstraintSample.Web/Controllers/US/ItemsController.cs create mode 100644 samples/ActionConstraintSample.Web/CountrySpecificAttribute.cs create mode 100644 samples/ActionConstraintSample.Web/Startup.cs create mode 100644 samples/ActionConstraintSample.Web/project.json create mode 100644 samples/ActionConstraintSample.Web/readme.md create mode 100644 samples/ActionConstraintSample.Web/wwwroot/web.config create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/ActionConstraintSampleTest.cs delete mode 100644 test/WebSites/RoutingWebSite/Controllers/Products/ProductsController.cs delete mode 100644 test/WebSites/RoutingWebSite/Controllers/Products/US/ProductsController.cs delete mode 100644 test/WebSites/RoutingWebSite/CountryNeutralAttribute.cs delete mode 100644 test/WebSites/RoutingWebSite/CountrySpecificAttribute.cs diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 2497517719..d5b452ae82 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.24711.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -99,6 +99,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "JsonPatchSample.Web", "samp EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LocalizationSample.Web", "samples\LocalizationSample.Web\LocalizationSample.Web.xproj", "{FCFE6024-2720-49B4-8257-9DBC6114F0F1}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionConstraintSample.Web", "samples\ActionConstraintSample.Web\ActionConstraintSample.Web.xproj", "{EE0BD773-4D47-4AA8-8472-5A938A3953BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -591,6 +593,18 @@ Global {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|Mixed Platforms.Build.0 = Release|Any CPU {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|x86.ActiveCfg = Release|Any CPU {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|x86.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|x86.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Any CPU.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|x86.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -638,5 +652,6 @@ Global {EA34877F-1AC1-42B7-B4E6-15A093F40CAE} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {DAB1252D-577C-4912-98BE-1A812BF83F86} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {FCFE6024-2720-49B4-8257-9DBC6114F0F1} = {DAAE4C74-D06F-4874-A166-33305D2643CE} + {EE0BD773-4D47-4AA8-8472-5A938A3953BA} = {DAAE4C74-D06F-4874-A166-33305D2643CE} EndGlobalSection EndGlobal diff --git a/Mvc.sln b/Mvc.sln index 611eb28d1a..9ab0f304cb 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.24711.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -142,6 +142,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "JsonPatchSample.Web", "samp EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LocalizationSample.Web", "samples\LocalizationSample.Web\LocalizationSample.Web.xproj", "{FCFE6024-2720-49B4-8257-9DBC6114F0F1}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionConstraintSample.Web", "samples\ActionConstraintSample.Web\ActionConstraintSample.Web.xproj", "{EE0BD773-4D47-4AA8-8472-5A938A3953BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -851,6 +853,18 @@ Global {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|Mixed Platforms.Build.0 = Release|Any CPU {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|x86.ActiveCfg = Release|Any CPU {FCFE6024-2720-49B4-8257-9DBC6114F0F1}.Release|x86.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Debug|x86.Build.0 = Debug|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Any CPU.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|x86.ActiveCfg = Release|Any CPU + {EE0BD773-4D47-4AA8-8472-5A938A3953BA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -920,5 +934,6 @@ Global {EA34877F-1AC1-42B7-B4E6-15A093F40CAE} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {DAB1252D-577C-4912-98BE-1A812BF83F86} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {FCFE6024-2720-49B4-8257-9DBC6114F0F1} = {DAAE4C74-D06F-4874-A166-33305D2643CE} + {EE0BD773-4D47-4AA8-8472-5A938A3953BA} = {DAAE4C74-D06F-4874-A166-33305D2643CE} EndGlobalSection EndGlobal diff --git a/samples/ActionConstraintSample.Web/ActionConstraintSample.Web.xproj b/samples/ActionConstraintSample.Web/ActionConstraintSample.Web.xproj new file mode 100644 index 0000000000..8d03bc7958 --- /dev/null +++ b/samples/ActionConstraintSample.Web/ActionConstraintSample.Web.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + ee0bd773-4d47-4aa8-8472-5a938a3953ba + ActionConstraintSample.Web + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + + + + + + diff --git a/samples/ActionConstraintSample.Web/Controllers/ItemsController.cs b/samples/ActionConstraintSample.Web/Controllers/ItemsController.cs new file mode 100644 index 0000000000..5a1eec61cd --- /dev/null +++ b/samples/ActionConstraintSample.Web/Controllers/ItemsController.cs @@ -0,0 +1,15 @@ +// 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.AspNet.Mvc; + +namespace ActionConstraintSample.Web.Controllers +{ + public class ItemsController : Controller + { + public IActionResult GetItems() + { + return Content("Hello from everywhere"); + } + } +} \ No newline at end of file diff --git a/samples/ActionConstraintSample.Web/Controllers/US/ItemsController.cs b/samples/ActionConstraintSample.Web/Controllers/US/ItemsController.cs new file mode 100644 index 0000000000..7115344753 --- /dev/null +++ b/samples/ActionConstraintSample.Web/Controllers/US/ItemsController.cs @@ -0,0 +1,16 @@ +// 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.AspNet.Mvc; + +namespace ActionConstraintSample.Web.Controllers.US +{ + [CountrySpecific("US")] + public class ItemsController : Controller + { + public IActionResult GetItems() + { + return Content("Hello from US"); + } + } +} \ No newline at end of file diff --git a/samples/ActionConstraintSample.Web/CountrySpecificAttribute.cs b/samples/ActionConstraintSample.Web/CountrySpecificAttribute.cs new file mode 100644 index 0000000000..17cfc5ed7a --- /dev/null +++ b/samples/ActionConstraintSample.Web/CountrySpecificAttribute.cs @@ -0,0 +1,35 @@ +// 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 Microsoft.AspNet.Mvc.ActionConstraints; +using Microsoft.AspNet.Mvc.Infrastructure; + +namespace ActionConstraintSample.Web +{ + public class CountrySpecificAttribute : RouteConstraintAttribute, IActionConstraint + { + private readonly string _countryCode; + public CountrySpecificAttribute(string countryCode) + : base("country", countryCode, blockNonAttributedActions: false) + { + _countryCode = countryCode; + } + + public int Order + { + get + { + return 0; + } + } + + public bool Accept(ActionConstraintContext context) + { + return string.Equals( + context.RouteContext.RouteData.Values["country"].ToString(), + _countryCode, + StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/samples/ActionConstraintSample.Web/Startup.cs b/samples/ActionConstraintSample.Web/Startup.cs new file mode 100644 index 0000000000..8d5a8a5a2e --- /dev/null +++ b/samples/ActionConstraintSample.Web/Startup.cs @@ -0,0 +1,29 @@ +// 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.AspNet.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace ActionConstraintSample.Web +{ + public class Startup + { + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvc(routes => + { + routes.MapRoute( + "items", + "Items/{country}/{action}", + defaults: new { controller = "Items" }); + routes.MapRoute("default", "{controller}/{action}"); + }); + } + } +} diff --git a/samples/ActionConstraintSample.Web/project.json b/samples/ActionConstraintSample.Web/project.json new file mode 100644 index 0000000000..fd0f9b0fc3 --- /dev/null +++ b/samples/ActionConstraintSample.Web/project.json @@ -0,0 +1,15 @@ +{ + "commands": { + "web": "Microsoft.AspNet.Server.Kestrel", + "weblistener": "Microsoft.AspNet.Server.WebListener" + }, + "dependencies": { + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*" + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + } +} \ No newline at end of file diff --git a/samples/ActionConstraintSample.Web/readme.md b/samples/ActionConstraintSample.Web/readme.md new file mode 100644 index 0000000000..b469272c44 --- /dev/null +++ b/samples/ActionConstraintSample.Web/readme.md @@ -0,0 +1,4 @@ +ActionConstraintSample.Web +=== + +This web site illustrates how to setup multiple controllers/actions with same name that is selected based on specific route values using IActionConstraint. diff --git a/samples/ActionConstraintSample.Web/wwwroot/web.config b/samples/ActionConstraintSample.Web/wwwroot/web.config new file mode 100644 index 0000000000..8485f6719f --- /dev/null +++ b/samples/ActionConstraintSample.Web/wwwroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteDataActionConstraint.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteDataActionConstraint.cs index 1244ab42c4..65e22c0de9 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteDataActionConstraint.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteDataActionConstraint.cs @@ -45,20 +45,6 @@ namespace Microsoft.AspNet.Mvc.Routing } } - /// - /// Create a catch all constraint for the given key. - /// - /// Route key. - /// a that represents a catch all constraint. - public static RouteDataActionConstraint CreateCatchAll(string routeKey) - { - var c = new RouteDataActionConstraint(routeKey); - c.KeyHandling = RouteKeyHandling.CatchAll; - c.RouteValue = string.Empty; - - return c; - } - /// /// The route key this constraint matches against. /// diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteKeyHandling.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteKeyHandling.cs index aee03ff0e4..21675a11bb 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteKeyHandling.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Routing/RouteKeyHandling.cs @@ -14,12 +14,5 @@ namespace Microsoft.AspNet.Mvc.Routing /// Requires that the key will not be in the route values. /// DenyKey, - - /// - /// Requires that the key will be in the route values, but ignore the content. - /// Constraints with this value are considered less important than ones with - /// - /// - CatchAll, } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Controllers/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/Controllers/ControllerActionDescriptorBuilder.cs index a2dfa778f5..49852f9a5b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controllers/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controllers/ControllerActionDescriptorBuilder.cs @@ -464,18 +464,9 @@ namespace Microsoft.AspNet.Mvc.Controllers // Skip duplicates if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { - if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll) - { - actionDescriptor.RouteConstraints.Add( - RouteDataActionConstraint.CreateCatchAll( - constraintAttribute.RouteKey)); - } - else - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - constraintAttribute.RouteKey, - constraintAttribute.RouteValue)); - } + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + constraintAttribute.RouteKey, + constraintAttribute.RouteValue)); } } @@ -489,18 +480,9 @@ namespace Microsoft.AspNet.Mvc.Controllers // Skip duplicates - this also means that a value on the action will take precedence if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { - if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll) - { - actionDescriptor.RouteConstraints.Add( - RouteDataActionConstraint.CreateCatchAll( - constraintAttribute.RouteKey)); - } - else - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - constraintAttribute.RouteKey, - constraintAttribute.RouteValue)); - } + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + constraintAttribute.RouteKey, + constraintAttribute.RouteValue)); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/RouteConstraintAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/RouteConstraintAttribute.cs index 0f31c57d24..d2d48d84d2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/RouteConstraintAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/RouteConstraintAttribute.cs @@ -21,16 +21,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure public abstract class RouteConstraintAttribute : Attribute, IRouteConstraintProvider { /// - /// Creates a new . + /// Creates a new with set as + /// . /// /// The route value key. - /// - /// The value. Must be - /// or . - /// - protected RouteConstraintAttribute( - string routeKey, - RouteKeyHandling keyHandling) + protected RouteConstraintAttribute(string routeKey) { if (routeKey == null) { @@ -38,16 +33,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure } RouteKey = routeKey; - RouteKeyHandling = keyHandling; - - if (keyHandling != RouteKeyHandling.CatchAll && - keyHandling != RouteKeyHandling.DenyKey) - { - var message = Resources.FormatRouteConstraintAttribute_InvalidKeyHandlingValue( - Enum.GetName(typeof(RouteKeyHandling), RouteKeyHandling.CatchAll), - Enum.GetName(typeof(RouteKeyHandling), RouteKeyHandling.DenyKey)); - throw new ArgumentException(message, nameof(keyHandling)); - } + RouteKeyHandling = RouteKeyHandling.DenyKey; } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 777e416d2c..be8aff9c5b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -474,22 +474,6 @@ namespace Microsoft.AspNet.Mvc.Core return GetString("AttributeRoute_TokenReplacement_UnescapedBraceInToken"); } - /// - /// The value must be either '{0}' or '{1}'. - /// - internal static string RouteConstraintAttribute_InvalidKeyHandlingValue - { - get { return GetString("RouteConstraintAttribute_InvalidKeyHandlingValue"); } - } - - /// - /// The value must be either '{0}' or '{1}'. - /// - internal static string FormatRouteConstraintAttribute_InvalidKeyHandlingValue(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintAttribute_InvalidKeyHandlingValue"), p0, p1); - } - /// /// Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 5a0b351d07..45e61ee6b4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -207,9 +207,6 @@ An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape. - - The value must be either '{0}' or '{1}'. - Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code. diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/ActionSelectionDecisionTree.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/ActionSelectionDecisionTree.cs index 1faac404d2..80ce81af5d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/ActionSelectionDecisionTree.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/ActionSelectionDecisionTree.cs @@ -41,69 +41,7 @@ namespace Microsoft.AspNet.Mvc.Routing var results = new List(); Walk(results, routeValues, _root); - // If we have a match that isn't using catch-all, then it's considered better than matches with catch all - // so filter those out. - var hasNonCatchAll = false; - - // The common case for MVC has no catch-alls, so avoid allocating. - List filtered = null; - - // Perf: Avoid allocations - for (var i = 0; i < results.Count; i++) - { - var action = results[i]; - - var actionHasCatchAll = false; - if (action.RouteConstraints != null) - { - for (var j = 0; j < action.RouteConstraints.Count; j++) - { - var constraint = action.RouteConstraints[j]; - if (constraint.KeyHandling == RouteKeyHandling.CatchAll) - { - actionHasCatchAll = true; - break; - } - } - } - - if (hasNonCatchAll && actionHasCatchAll) - { - // Do nothing - we've already found a better match. - } - else if (actionHasCatchAll) - { - if (filtered == null) - { - filtered = new List(); - } - - filtered.Add(action); - } - else if (hasNonCatchAll) - { - Debug.Assert(filtered != null); - filtered.Add(action); - } - else - { - // This is the first non-catch-all we've found. - hasNonCatchAll = true; - - if (filtered == null) - { - filtered = new List(); - } - else - { - filtered.Clear(); - } - - filtered.Add(action); - } - } - - return filtered ?? results; + return results; } private void Walk( @@ -122,20 +60,13 @@ namespace Microsoft.AspNet.Mvc.Routing var key = criterion.Key; object value; - var hasValue = routeValues.TryGetValue(key, out value); + routeValues.TryGetValue(key, out value); DecisionTreeNode branch; if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch)) { Walk(results, routeValues, branch); } - - // If there's a fallback node we always need to process it when we have a value. We'll prioritize - // non-fallback matches later in the process. - if (hasValue && criterion.Fallback != null) - { - Walk(results, routeValues, criterion.Fallback); - } } } @@ -157,19 +88,15 @@ namespace Microsoft.AspNet.Mvc.Routing foreach (var constraint in item.RouteConstraints) { DecisionCriterionValue value; - if (constraint.KeyHandling == RouteKeyHandling.CatchAll) - { - value = new DecisionCriterionValue(value: null, isCatchAll: true); - } - else if (constraint.KeyHandling == RouteKeyHandling.DenyKey) + if (constraint.KeyHandling == RouteKeyHandling.DenyKey) { // null and string.Empty are equivalent for route values, so just treat nulls as // string.Empty. - value = new DecisionCriterionValue(value: string.Empty, isCatchAll: false); + value = new DecisionCriterionValue(value: string.Empty); } else if (constraint.KeyHandling == RouteKeyHandling.RequireKey) { - value = new DecisionCriterionValue(value: constraint.RouteValue, isCatchAll: false); + value = new DecisionCriterionValue(value: constraint.RouteValue); } else { diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs index 202030b839..4627cc3e3a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs @@ -154,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.Razor { return null; } - else if (constraint.KeyHandling == RouteKeyHandling.RequireKey) + else { normalizedValue = constraint.RouteValue; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs index f424b059dd..a4221d16c4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs @@ -347,92 +347,6 @@ namespace Microsoft.AspNet.Mvc.Infrastructure Assert.Same(action, best); } - [Fact] - public async Task SelectAsync_WithCatchAll_PrefersNonCatchAll() - { - // Arrange - var actions = new ActionDescriptor[] - { - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - }; - - actions[0].RouteConstraints.Add(new RouteDataActionConstraint("country", "CA")); - actions[1].RouteConstraints.Add(new RouteDataActionConstraint("country", "US")); - actions[2].RouteConstraints.Add(RouteDataActionConstraint.CreateCatchAll("country")); - - var selector = CreateSelector(actions); - var context = CreateRouteContext("GET"); - - context.RouteData.Values.Add("controller", "Store"); - context.RouteData.Values.Add("action", "Buy"); - context.RouteData.Values.Add("country", "CA"); - - // Act - var action = await selector.SelectAsync(context); - - // Assert - Assert.Same(action, actions[0]); - } - - [Fact] - public async Task SelectAsync_WithCatchAll_CatchAllIsOnlyMatch() - { - // Arrange - var actions = new ActionDescriptor[] - { - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - }; - - actions[0].RouteConstraints.Add(new RouteDataActionConstraint("country", "CA")); - actions[1].RouteConstraints.Add(new RouteDataActionConstraint("country", "US")); - actions[2].RouteConstraints.Add(RouteDataActionConstraint.CreateCatchAll("country")); - - var selector = CreateSelector(actions); - var context = CreateRouteContext("GET"); - - context.RouteData.Values.Add("controller", "Store"); - context.RouteData.Values.Add("action", "Buy"); - context.RouteData.Values.Add("country", "DE"); - - // Act - var action = await selector.SelectAsync(context); - - // Assert - Assert.Same(action, actions[2]); - } - - [Fact] - public async Task SelectAsync_WithCatchAll_NoMatch() - { - // Arrange - var actions = new ActionDescriptor[] - { - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - CreateAction(area: null, controller: "Store", action: "Buy"), - }; - - actions[0].RouteConstraints.Add(new RouteDataActionConstraint("country", "CA")); - actions[1].RouteConstraints.Add(new RouteDataActionConstraint("country", "US")); - actions[2].RouteConstraints.Add(RouteDataActionConstraint.CreateCatchAll("country")); - - var selector = CreateSelector(actions); - var context = CreateRouteContext("GET"); - - context.RouteData.Values.Add("controller", "Store"); - context.RouteData.Values.Add("action", "Buy"); - - // Act - var action = await selector.SelectAsync(context); - - // Assert - Assert.Null(action); - } - [Fact] public async Task SelectAsync_Ambiguous() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/RouteDataActionConstraintTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/RouteDataActionConstraintTest.cs index 818c841c5b..c75173b8f2 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/RouteDataActionConstraintTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/RouteDataActionConstraintTest.cs @@ -37,15 +37,5 @@ namespace Microsoft.AspNet.Mvc.Infrastructure Assert.Equal(routeDataConstraint.KeyHandling, RouteKeyHandling.RequireKey); Assert.Equal(routeDataConstraint.RouteValue, "value"); } - - [Fact] - public void RouteDataActionConstraint_CatchAll() - { - var routeDataConstraint = RouteDataActionConstraint.CreateCatchAll("key"); - - Assert.Equal(routeDataConstraint.RouteKey, "key"); - Assert.Equal(routeDataConstraint.KeyHandling, RouteKeyHandling.CatchAll); - Assert.Equal(routeDataConstraint.RouteValue, string.Empty); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionConstraintSampleTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionConstraintSampleTest.cs new file mode 100644 index 0000000000..e2d72b329e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionConstraintSampleTest.cs @@ -0,0 +1,52 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class ActionConstraintSampleTest : IClassFixture> + { + public ActionConstraintSampleTest(MvcTestFixture fixture) + { + Client = fixture.Client; + } + + public HttpClient Client { get; } + + [Fact] + public async Task ControllerWithActionConstraint_SelectsSpecificController() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Items/US/GetItems"); + request.Headers.Add("User", "Blah"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Hello from US", body); + } + + [Fact] + public async Task ControllerWithActionConstraint_NoMatchesFound_SelectsDefaultController() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Items/CA/GetItems"); + request.Headers.Add("User", "Blah"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Hello from everywhere", body); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs index 22403d710a..d30132a12a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs @@ -1089,90 +1089,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("/Admin/Users/All", result.Link); } - [Fact] - public async Task ControllerWithCatchAll_CanReachSpecificCountry() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Products/US/GetProducts"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Products/US/GetProducts", result.ExpectedUrls); - Assert.Equal("Products", result.Controller); - Assert.Equal("GetProducts", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "country", "US" }, - { "action", "GetProducts" }, - { "controller", "Products" }, - }, - result.RouteValues); - } - - // The 'default' route doesn't provide a value for {country} - [Fact] - public async Task ControllerWithCatchAll_CannotReachWithoutCountry() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Products/GetProducts"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task ControllerWithCatchAll_GenerateLinkForSpecificCountry() - { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "GetProducts", controller = "Products", country = "US" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - Assert.Equal("/api/Products/US/GetProducts", result.Link); - } - - [Fact] - public async Task ControllerWithCatchAll_GenerateLinkForFallback() - { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "GetProducts", controller = "Products", country = "CA" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - Assert.Equal("/api/Products/CA/GetProducts", result.Link); - } - - [Fact] - public async Task ControllerWithCatchAll_GenerateLink_FallsThroughWithoutCountry() - { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "GetProducts", controller = "Products", country = (string)null }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - Assert.Equal("/Products/GetProducts", result.Link); - } - [Theory] [InlineData("/Bank/Deposit", "PUT", "Deposit")] [InlineData("/Bank/Deposit", "POST", "Deposit")] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 2ed994214c..9cd18e4a4b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -7,6 +7,7 @@ "warningsAsErrors": true }, "dependencies": { + "ActionConstraintSample.Web": "1.0.0", "ApiExplorerWebSite": "1.0.0", "ApplicationModelWebSite": "1.0.0", "BasicWebSite": "1.0.0", diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs index 83b1f488ef..34e4ff08df 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs @@ -1577,34 +1577,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Test Assert.Null(result); } - [Fact] - public void GetNormalizedRouteValue_ReturnsRouteDataValue_IfRouteConstraintKeyHandlingIsCatchAll() - { - // Arrange - var key = "some-key"; - var actionDescriptor = new ActionDescriptor - { - RouteConstraints = new[] - { - RouteDataActionConstraint.CreateCatchAll(key) - } - }; - - var actionContext = new ActionContext - { - ActionDescriptor = actionDescriptor, - RouteData = new RouteData() - }; - - actionContext.RouteData.Values[key] = "route-value"; - - // Act - var result = RazorViewEngine.GetNormalizedRouteValue(actionContext, key); - - // Assert - Assert.Equal("route-value", result); - } - [Fact] public void GetNormalizedRouteValue_UsesRouteValueDefaults_IfAttributeRouted() { diff --git a/test/WebSites/RoutingWebSite/Controllers/Products/ProductsController.cs b/test/WebSites/RoutingWebSite/Controllers/Products/ProductsController.cs deleted file mode 100644 index 9333d2af28..0000000000 --- a/test/WebSites/RoutingWebSite/Controllers/Products/ProductsController.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.AspNet.Mvc; - -namespace RoutingWebSite.Products -{ - [CountryNeutral] - public class ProductsController : Controller - { - private readonly TestResponseGenerator _generator; - - public ProductsController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult GetProducts() - { - return _generator.Generate("/api/Products/CA/GetProducts"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Controllers/Products/US/ProductsController.cs b/test/WebSites/RoutingWebSite/Controllers/Products/US/ProductsController.cs deleted file mode 100644 index b47f8d593c..0000000000 --- a/test/WebSites/RoutingWebSite/Controllers/Products/US/ProductsController.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.AspNet.Mvc; - -namespace RoutingWebSite.Products.US -{ - [CountrySpecific("US")] - public class ProductsController : Controller - { - private readonly TestResponseGenerator _generator; - - public ProductsController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult GetProducts() - { - return _generator.Generate("/api/Products/US/GetProducts"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/CountryNeutralAttribute.cs b/test/WebSites/RoutingWebSite/CountryNeutralAttribute.cs deleted file mode 100644 index 7622948673..0000000000 --- a/test/WebSites/RoutingWebSite/CountryNeutralAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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.AspNet.Mvc.Infrastructure; -using Microsoft.AspNet.Mvc.Routing; - -namespace RoutingWebSite -{ - public class CountryNeutralAttribute : RouteConstraintAttribute - { - public CountryNeutralAttribute() - : base("country", RouteKeyHandling.CatchAll) - { - } - } -} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/CountrySpecificAttribute.cs b/test/WebSites/RoutingWebSite/CountrySpecificAttribute.cs deleted file mode 100644 index c544abf4b7..0000000000 --- a/test/WebSites/RoutingWebSite/CountrySpecificAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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.AspNet.Mvc.Infrastructure; - -namespace RoutingWebSite -{ - public class CountrySpecificAttribute : RouteConstraintAttribute - { - public CountrySpecificAttribute(string countryCode) - : base("country", countryCode, blockNonAttributedActions: false) - { - } - } -} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index ed86074c58..4acd9a15b2 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -31,11 +31,6 @@ namespace RoutingWebSite "{area:exists}/{controller}/{action}", new { controller = "Home", action = "Index" }); - routes.MapRoute( - "products", - "api/Products/{country}/{action}", - defaults: new { controller = "Products" }); - routes.MapRoute( "ActionAsMethod", "{controller}/{action}",