[Fixes #3430] Removed RouteKeyHandling.CatchAll

This commit is contained in:
Ajay Bhargav Baaskaran 2015-12-15 12:56:09 -08:00
parent 7c40759e32
commit 232b27ad5d
29 changed files with 249 additions and 453 deletions

View File

@ -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

17
Mvc.sln
View File

@ -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

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ee0bd773-4d47-4aa8-8472-5a938a3953ba</ProjectGuid>
<RootNamespace>ActionConstraintSample.Web</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
<DnxInvisibleContent Include="package.json" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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}");
});
}
}
}

View File

@ -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": { }
}
}

View File

@ -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.

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" startupTimeLimit="3600"/>
</system.webServer>
</configuration>

View File

@ -45,20 +45,6 @@ namespace Microsoft.AspNet.Mvc.Routing
}
}
/// <summary>
/// Create a catch all constraint for the given key.
/// </summary>
/// <param name="routeKey">Route key.</param>
/// <returns>a <see cref="RouteDataActionConstraint"/> that represents a catch all constraint.</returns>
public static RouteDataActionConstraint CreateCatchAll(string routeKey)
{
var c = new RouteDataActionConstraint(routeKey);
c.KeyHandling = RouteKeyHandling.CatchAll;
c.RouteValue = string.Empty;
return c;
}
/// <summary>
/// The route key this constraint matches against.
/// </summary>

View File

@ -14,12 +14,5 @@ namespace Microsoft.AspNet.Mvc.Routing
/// Requires that the key will not be in the route values.
/// </summary>
DenyKey,
/// <summary>
/// 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
/// <see cref="RequireKey"/>
/// </summary>
CatchAll,
}
}

View File

@ -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));
}
}

View File

@ -21,16 +21,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
public abstract class RouteConstraintAttribute : Attribute, IRouteConstraintProvider
{
/// <summary>
/// Creates a new <see cref="RouteConstraintAttribute"/>.
/// Creates a new <see cref="RouteConstraintAttribute"/> with <see cref="RouteKeyHandling"/> set as
/// <see cref="RouteKeyHandling.DenyKey"/>.
/// </summary>
/// <param name="routeKey">The route value key.</param>
/// <param name="keyHandling">
/// The <see cref="RouteKeyHandling"/> value. Must be <see cref="RouteKeyHandling.CatchAll "/>
/// or <see cref="RouteKeyHandling.DenyKey"/>.
/// </param>
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;
}
/// <summary>

View File

@ -474,22 +474,6 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("AttributeRoute_TokenReplacement_UnescapedBraceInToken");
}
/// <summary>
/// The value must be either '{0}' or '{1}'.
/// </summary>
internal static string RouteConstraintAttribute_InvalidKeyHandlingValue
{
get { return GetString("RouteConstraintAttribute_InvalidKeyHandlingValue"); }
}
/// <summary>
/// The value must be either '{0}' or '{1}'.
/// </summary>
internal static string FormatRouteConstraintAttribute_InvalidKeyHandlingValue(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintAttribute_InvalidKeyHandlingValue"), p0, p1);
}
/// <summary>
/// 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.
/// </summary>

View File

@ -207,9 +207,6 @@
<data name="AttributeRoute_TokenReplacement_UnescapedBraceInToken" xml:space="preserve">
<value>An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape.</value>
</data>
<data name="RouteConstraintAttribute_InvalidKeyHandlingValue" xml:space="preserve">
<value>The value must be either '{0}' or '{1}'.</value>
</data>
<data name="UnableToFindServices" xml:space="preserve">
<value>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.</value>
</data>

View File

@ -41,69 +41,7 @@ namespace Microsoft.AspNet.Mvc.Routing
var results = new List<ActionDescriptor>();
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<ActionDescriptor> 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<ActionDescriptor>();
}
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<ActionDescriptor>();
}
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<ActionDescriptor> 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
{

View File

@ -154,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
return null;
}
else if (constraint.KeyHandling == RouteKeyHandling.RequireKey)
else
{
normalizedValue = constraint.RouteValue;
}

View File

@ -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()
{

View File

@ -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);
}
}
}

View File

@ -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<MvcTestFixture<ActionConstraintSample.Web.Startup>>
{
public ActionConstraintSampleTest(MvcTestFixture<ActionConstraintSample.Web.Startup> 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);
}
}
}

View File

@ -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<RoutingResult>(body);
Assert.Contains("/api/Products/US/GetProducts", result.ExpectedUrls);
Assert.Equal("Products", result.Controller);
Assert.Equal("GetProducts", result.Action);
Assert.Equal(
new Dictionary<string, object>(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<RoutingResult>(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<RoutingResult>(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<RoutingResult>(body);
Assert.Equal("/Products/GetProducts", result.Link);
}
[Theory]
[InlineData("/Bank/Deposit", "PUT", "Deposit")]
[InlineData("/Bank/Deposit", "POST", "Deposit")]

View File

@ -7,6 +7,7 @@
"warningsAsErrors": true
},
"dependencies": {
"ActionConstraintSample.Web": "1.0.0",
"ApiExplorerWebSite": "1.0.0",
"ApplicationModelWebSite": "1.0.0",
"BasicWebSite": "1.0.0",

View File

@ -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()
{

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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}",