Skip to content

Commit 6949f1b

Browse files
author
Chris Martinez
committed
Fix action selection for failed response when lower precedence candidate might be erroroneously selected.
1 parent 94f261e commit 6949f1b

File tree

4 files changed

+34
-4
lines changed

4 files changed

+34
-4
lines changed

src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ActionSelectionResult.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ public class ActionSelectionResult
2121
/// <value>The total number of action selection iterations that have occurred. The default value is zero.</value>
2222
public int Iterations { get; private set; }
2323

24+
/// <summary>
25+
/// Gets a value indicating whether are higher precendence matches in previous iterations.
26+
/// </summary>
27+
/// <value>True if there are higher precendence matches in one or more previous iterations;
28+
/// otherwise, false. The default value is <c>false</c>.</value>
29+
public bool HasMatchesInPreviousIterations { get; private set; }
30+
2431
/// <summary>
2532
/// Gets the best action descriptor match.
2633
/// </summary>
@@ -142,6 +149,7 @@ public void EndIteration()
142149
}
143150

144151
++Iterations;
152+
HasMatchesInPreviousIterations |= matchingActions[key].Count > 0;
145153
}
146154

147155
/// <summary>
@@ -153,7 +161,7 @@ public void EndIteration()
153161
[CLSCompliant( false )]
154162
public bool TrySetBestMatch( ActionDescriptorMatch match )
155163
{
156-
if ( match == null )
164+
if ( match == null || HasMatchesInPreviousIterations )
157165
{
158166
return false;
159167
}

src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersionActionSelector.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ public virtual ActionDescriptor SelectBestCandidate( RouteContext context, IRead
120120
var selectedAction = finalMatches[0];
121121

122122
// note: short-circuit if the api version policy has already been applied to the match
123-
// and no other better match has already been selected
124-
if ( selectedAction.VersionPolicyIsApplied() && selectionResult.BestMatch == null )
123+
// and there are no other matches in a previous iteration which would take precendence
124+
if ( selectedAction.VersionPolicyIsApplied() && !selectionResult.HasMatchesInPreviousIterations )
125125
{
126126
httpContext.ApiVersionProperties().ApiVersion = selectionContext.RequestedVersion;
127127
return selectedAction;

test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Basic/Controllers/OverlappingRouteTemplateController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,11 @@ public class OverlappingRouteTemplateController : Controller
1212

1313
[HttpGet( "{id:int}/children" )]
1414
public IActionResult Get( int id ) => Ok( new { id } );
15+
16+
[HttpGet( "{id:int}/ambiguous" )]
17+
public IActionResult Ambiguous( int id ) => Ok();
18+
19+
[HttpGet( "{id:int}/ambiguous" )]
20+
public IActionResult Ambiguous2( int id ) => Ok();
1521
}
1622
}

test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Basic/given a versioned Controller/when two route templates overlap.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
namespace given_a_versioned_Controller
22
{
33
using FluentAssertions;
4-
using Microsoft.AspNetCore.Mvc;
54
using Microsoft.AspNetCore.Mvc.Basic;
65
using Microsoft.AspNetCore.Mvc.Basic.Controllers;
6+
using Microsoft.AspNetCore.Mvc.Internal;
7+
using System;
78
using System.Reflection;
89
using System.Threading.Tasks;
910
using Xunit;
@@ -47,5 +48,20 @@ public async Task then_the_higher_precedence_route_should_be_selected_during_the
4748
result1.Should().Be( "{\"id\":42,\"childId\":\"abc\"}" );
4849
result2.Should().Be( "{\"id\":42}" );
4950
}
51+
52+
[Fact]
53+
public async Task then_the_higher_precedence_route_should_result_in_ambiguous_action_exception_during_the_second_request()
54+
{
55+
// arrange
56+
var response = await Client.GetAsync( "api/v1/values/42/abc" );
57+
var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
58+
59+
// act
60+
Func<Task> act = async () => await Client.GetAsync( "api/v1/values/42/ambiguous" );
61+
62+
// assert
63+
result1.Should().Be( "{\"id\":42,\"childId\":\"abc\"}" );
64+
act.ShouldThrow<AmbiguousActionException>();
65+
}
5066
}
5167
}

0 commit comments

Comments
 (0)