Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
[Fixes #7959] Conventional routing with custom templates not working …
Browse files Browse the repository at this point in the history
…when you have area attributes
  • Loading branch information
kichalla committed Jul 9, 2018
1 parent 82f7f2a commit bd995d4
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 2 deletions.
11 changes: 9 additions & 2 deletions src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
values[i] = value as string ?? Convert.ToString(value);
}
}

if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) ||
cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues))
{
Expand Down Expand Up @@ -441,7 +441,14 @@ public int GetHashCode(string[] obj)
var hash = new HashCodeCombiner();
for (var i = 0; i < obj.Length; i++)
{
hash.Add(obj[i], _valueComparer);
var o = obj[i];

// Route values define null and "" to be equivalent.
if (string.IsNullOrEmpty(o))
{
o = null;
}
hash.Add(o, _valueComparer);
}

return hash.CombinedHash;
Expand Down
170 changes: 170 additions & 0 deletions test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,176 @@ public void SelectCandidates_Match_CaseInsensitiveMatch_IncludesAllCaseInsensiti
Assert.Equal(expected, candidates);
}

[Fact]
public void SelectCandidates_Match_CaseSensitiveMatch_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};

var selector = CreateSelector(actions);

var routeContext = CreateRouteContext("GET");
// Example: In conventional route, one could set non-inline defaults
// new { area = "", controller = "Home", action = "Index" }
routeContext.RouteData.Values.Add("area", "");
routeContext.RouteData.Values.Add("controller", "Home");
routeContext.RouteData.Values.Add("action", "Index");

// Act
var candidates = selector.SelectCandidates(routeContext);

// Assert
var action = Assert.Single(candidates);
Assert.Same(actions[0], action);
}

[Fact]
public void SelectCandidates_Match_CaseInsensitiveMatch_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};

var selector = CreateSelector(actions);

var routeContext = CreateRouteContext("GET");
// Example: In conventional route, one could set non-inline defaults
// new { area = "", controller = "Home", action = "Index" }
routeContext.RouteData.Values.Add("area", "");
routeContext.RouteData.Values.Add("controller", "HoMe");
routeContext.RouteData.Values.Add("action", "InDeX");

// Act
var candidates = selector.SelectCandidates(routeContext);

// Assert
var action = Assert.Single(candidates);
Assert.Same(actions[0], action);
}

[Fact]
public void SelectCandidates_Match_MatchesOnNull()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};

var selector = CreateSelector(actions);

var routeContext = CreateRouteContext("GET");
// Example: In conventional route, one could set non-inline defaults
// new { area = (string)null, controller = "Foo", action = "Index" }
routeContext.RouteData.Values.Add("area", null);
routeContext.RouteData.Values.Add("controller", "Home");
routeContext.RouteData.Values.Add("action", "Index");

// Act
var candidates = selector.SelectCandidates(routeContext);

// Assert
var action = Assert.Single(candidates);
Assert.Same(actions[0], action);
}

[Fact]
public void SelectCandidates_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "foo", "" },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};

var selector = CreateSelector(actions);

var routeContext = CreateRouteContext("GET");
// Example: In conventional route, one could set non-inline defaults
// new { area = (string)null, controller = "Home", action = "Index" }
routeContext.RouteData.Values.Add("foo", "");
routeContext.RouteData.Values.Add("controller", "Home");
routeContext.RouteData.Values.Add("action", "Index");

// Act
var candidates = selector.SelectCandidates(routeContext);

// Assert
var action = Assert.Single(candidates);
Assert.Same(actions[0], action);
}

[Fact]
public void SelectCandidates_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnNull()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "foo", "" },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};

var selector = CreateSelector(actions);

var routeContext = CreateRouteContext("GET");
// Example: In conventional route, one could set non-inline defaults
// new { area = (string)null, controller = "Home", action = "Index" }
routeContext.RouteData.Values.Add("foo", null);
routeContext.RouteData.Values.Add("controller", "Home");
routeContext.RouteData.Values.Add("action", "Index");

// Act
var candidates = selector.SelectCandidates(routeContext);

// Assert
var action = Assert.Single(candidates);
Assert.Same(actions[0], action);
}

[Fact]
public void SelectBestCandidate_AmbiguousActions_LogIsCorrect()
{
Expand Down

0 comments on commit bd995d4

Please sign in to comment.