Discard ambient values during link generation if the values do not match explicit values

[Fixes #544] Link generation: Discard ambient values unless routing to the same address
This commit is contained in:
Kiran Challa 2018-07-27 05:09:14 -07:00 committed by Kiran Challa
parent 3d4a66ab8d
commit 85c7bd8fac
4 changed files with 656 additions and 255 deletions

View File

@ -92,7 +92,9 @@ namespace Microsoft.AspNetCore.Routing
var templateValuesResult = templateBinder.GetValues(
ambientValues: context.AmbientValues,
values: context.ExplicitValues);
explicitValues: context.ExplicitValues,
endpoint.RequiredValues.Keys);
if (templateValuesResult == null)
{
// We're missing one of the required values for this route.

View File

@ -3,6 +3,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text.Encodings.Web;
@ -64,157 +65,71 @@ namespace Microsoft.AspNetCore.Routing.Template
// Step 1: Get the list of values we're going to try to use to match and generate this URI
public TemplateValuesResult GetValues(RouteValueDictionary ambientValues, RouteValueDictionary values)
{
return GetValues(ambientValues: ambientValues, explicitValues: values, requiredKeys: null);
}
internal TemplateValuesResult GetValues(
RouteValueDictionary ambientValues,
RouteValueDictionary explicitValues,
IEnumerable<string> requiredKeys)
{
var context = new TemplateBindingContext(_defaults);
// Find out which entries in the URI are valid for the URI we want to generate.
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
// specified that b="9", then we need to invalidate everything after it. The new
// values should then be a="1", b="9", c=<no value>.
//
// We also handle the case where a parameter is optional but has no value - we shouldn't
// accept additional parameters that appear *after* that parameter.
for (var i = 0; i < _template.Parameters.Count; i++)
var canCopyParameterAmbientValues = true;
// In case a route template is not parameterized, we do not know what the required parameters are, so look
// at required values of an endpoint to get the key names and then use them to decide if ambient values
// should be used.
// For example, in case of MVC it flattens out a route template like below
// {controller}/{action}/{id?}
// to
// Products/Index/{id?},
// defaults: new { controller = "Products", action = "Index" },
// requiredValues: new { controller = "Products", action = "Index" }
// In the above example, "controller" and "action" are no longer parameters.
if (requiredKeys != null)
{
var parameter = _template.Parameters[i];
canCopyParameterAmbientValues = CanCopyParameterAmbientValues(
ambientValues: ambientValues,
explicitValues: explicitValues,
requiredKeys);
}
// If it's a parameter subsegment, examine the current value to see if it matches the new value
var parameterName = parameter.Name;
object newParameterValue;
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
object currentParameterValue = null;
var hasCurrentParameterValue = ambientValues != null &&
ambientValues.TryGetValue(parameterName, out currentParameterValue);
if (hasNewParameterValue && hasCurrentParameterValue)
{
if (!RoutePartsEqual(currentParameterValue, newParameterValue))
{
// Stop copying current values when we find one that doesn't match
break;
}
}
if (!hasNewParameterValue &&
!hasCurrentParameterValue &&
_defaults?.ContainsKey(parameter.Name) != true)
{
// This is an unsatisfied parameter value and there are no defaults. We might still
// be able to generate a URL but we should stop 'accepting' ambient values.
//
// This might be a case like:
// template: a/{b?}/{c?}
// ambient: { c = 17 }
// values: { }
//
// We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
// we can't use it.
//
// In the example above we should fall into this block for 'b'.
break;
}
// If the parameter is a match, add it to the list of values we will use for URI generation
if (hasNewParameterValue)
{
if (IsRoutePartNonEmpty(newParameterValue))
{
context.Accept(parameterName, newParameterValue);
}
}
else
{
if (hasCurrentParameterValue)
{
context.Accept(parameterName, currentParameterValue);
}
}
if (canCopyParameterAmbientValues)
{
// Copy ambient values when no explicit values are provided
CopyParameterAmbientValues(
ambientValues: ambientValues,
explicitValues: explicitValues,
context);
}
// Add all remaining new values to the list of values we will use for URI generation
foreach (var kvp in values)
{
if (IsRoutePartNonEmpty(kvp.Value))
{
context.Accept(kvp.Key, kvp.Value);
}
}
CopyNonParamaterExplicitValues(explicitValues, context);
// Accept all remaining default values if they match a required parameter
for (var i = 0; i < _template.Parameters.Count; i++)
CopyParameterDefaultValues(context);
if (!AllRequiredParametersHaveValue(context))
{
var parameter = _template.Parameters[i];
if (parameter.IsOptional || parameter.IsCatchAll)
{
continue;
}
if (context.NeedsValue(parameter.Name))
{
// Add the default value only if there isn't already a new value for it and
// only if it actually has a default value, which we determine based on whether
// the parameter value is required.
context.AcceptDefault(parameter.Name);
}
}
// Validate that all required parameters have a value.
for (var i = 0; i < _template.Parameters.Count; i++)
{
var parameter = _template.Parameters[i];
if (parameter.IsOptional || parameter.IsCatchAll)
{
continue;
}
if (!context.AcceptedValues.ContainsKey(parameter.Name))
{
// We don't have a value for this parameter, so we can't generate a url.
return null;
}
return null;
}
// Any default values that don't appear as parameters are treated like filters. Any new values
// provided must match these defaults.
foreach (var filter in _filters)
if (!FiltersMatch(explicitValues))
{
var parameter = GetParameter(filter.Key);
if (parameter != null)
{
continue;
}
object value;
if (values.TryGetValue(filter.Key, out value))
{
if (!RoutePartsEqual(value, filter.Value))
{
// If there is a non-parameterized value in the route and there is a
// new value for it and it doesn't match, this route won't match.
return null;
}
}
return null;
}
// Add any ambient values that don't match parameters - they need to be visible to constraints
// but they will ignored by link generation.
var combinedValues = new RouteValueDictionary(context.AcceptedValues);
if (ambientValues != null)
{
foreach (var kvp in ambientValues)
{
if (IsRoutePartNonEmpty(kvp.Value))
{
var parameter = GetParameter(kvp.Key);
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
{
combinedValues.Add(kvp.Key, kvp.Value);
}
}
}
}
CopyNonParameterAmbientValues(
ambientValues: ambientValues,
combinedValues: combinedValues,
context);
return new TemplateValuesResult()
{
@ -255,21 +170,18 @@ namespace Microsoft.AspNetCore.Routing.Template
else if (part.IsParameter)
{
// If it's a parameter, get its value
object value;
var hasValue = acceptedValues.TryGetValue(part.Name, out value);
var hasValue = acceptedValues.TryGetValue(part.Name, out var value);
if (hasValue)
{
acceptedValues.Remove(part.Name);
}
var isSameAsDefault = false;
object defaultValue;
if (_defaults != null && _defaults.TryGetValue(part.Name, out defaultValue))
if (_defaults != null &&
_defaults.TryGetValue(part.Name, out var defaultValue) &&
RoutePartsEqual(value, defaultValue))
{
if (RoutePartsEqual(value, defaultValue))
{
isSameAsDefault = true;
}
isSameAsDefault = true;
}
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
@ -414,6 +326,196 @@ namespace Microsoft.AspNetCore.Routing.Template
}
}
private bool CanCopyParameterAmbientValues(
RouteValueDictionary ambientValues,
RouteValueDictionary explicitValues,
IEnumerable<string> requiredKeys)
{
foreach (var keyName in requiredKeys)
{
if (!explicitValues.TryGetValue(keyName, out var explicitValue))
{
continue;
}
object ambientValue = null;
var hasAmbientValue = ambientValues != null &&
ambientValues.TryGetValue(keyName, out ambientValue);
// This indicates an explicit value was provided whose key does not exist in ambient values
// Example: Link from controller-action to a page or vice versa.
if (!hasAmbientValue)
{
return false;
}
if (!RoutePartsEqual(ambientValue, explicitValue))
{
return false;
}
}
return true;
}
private void CopyParameterAmbientValues(
RouteValueDictionary ambientValues,
RouteValueDictionary explicitValues,
TemplateBindingContext context)
{
// Find out which entries in the URI are valid for the URI we want to generate.
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
// specified that b="9", then we need to invalidate everything after it. The new
// values should then be a="1", b="9", c=<no value>.
//
// We also handle the case where a parameter is optional but has no value - we shouldn't
// accept additional parameters that appear *after* that parameter.
for (var i = 0; i < _template.Parameters.Count; i++)
{
var parameter = _template.Parameters[i];
// If it's a parameter subsegment, examine the current value to see if it matches the new value
var parameterName = parameter.Name;
var hasExplicitValue = explicitValues.TryGetValue(parameterName, out var explicitValue);
object ambientValue = null;
var hasAmbientValue = ambientValues != null &&
ambientValues.TryGetValue(parameterName, out ambientValue);
if (hasExplicitValue && hasAmbientValue && !RoutePartsEqual(ambientValue, explicitValue))
{
// Stop copying current values when we find one that doesn't match
break;
}
if (!hasExplicitValue &&
!hasAmbientValue &&
_defaults?.ContainsKey(parameter.Name) != true)
{
// This is an unsatisfied parameter value and there are no defaults. We might still
// be able to generate a URL but we should stop 'accepting' ambient values.
//
// This might be a case like:
// template: a/{b?}/{c?}
// ambient: { c = 17 }
// values: { }
//
// We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
// we can't use it.
//
// In the example above we should fall into this block for 'b'.
break;
}
// If the parameter is a match, add it to the list of values we will use for URI generation
if (hasExplicitValue)
{
if (IsRoutePartNonEmpty(explicitValue))
{
context.Accept(parameterName, explicitValue);
}
}
else if (hasAmbientValue)
{
context.Accept(parameterName, ambientValue);
}
}
}
private void CopyNonParamaterExplicitValues(RouteValueDictionary explicitValues, TemplateBindingContext context)
{
foreach (var kvp in explicitValues)
{
if (IsRoutePartNonEmpty(kvp.Value))
{
context.Accept(kvp.Key, kvp.Value);
}
}
}
private void CopyParameterDefaultValues(TemplateBindingContext context)
{
for (var i = 0; i < _template.Parameters.Count; i++)
{
var parameter = _template.Parameters[i];
if (parameter.IsOptional || parameter.IsCatchAll)
{
continue;
}
if (context.NeedsValue(parameter.Name))
{
// Add the default value only if there isn't already a new value for it and
// only if it actually has a default value, which we determine based on whether
// the parameter value is required.
context.AcceptDefault(parameter.Name);
}
}
}
private bool AllRequiredParametersHaveValue(TemplateBindingContext context)
{
for (var i = 0; i < _template.Parameters.Count; i++)
{
var parameter = _template.Parameters[i];
if (parameter.IsOptional || parameter.IsCatchAll)
{
continue;
}
if (!context.AcceptedValues.ContainsKey(parameter.Name))
{
// We don't have a value for this parameter, so we can't generate a url.
return false;
}
}
return true;
}
private bool FiltersMatch(RouteValueDictionary explicitValues)
{
foreach (var filter in _filters)
{
var parameter = GetParameter(filter.Key);
if (parameter != null)
{
continue;
}
if (explicitValues.TryGetValue(filter.Key, out var value) && !RoutePartsEqual(value, filter.Value))
{
// If there is a non-parameterized value in the route and there is a
// new value for it and it doesn't match, this route won't match.
return false;
}
}
return true;
}
private void CopyNonParameterAmbientValues(
RouteValueDictionary ambientValues,
RouteValueDictionary combinedValues,
TemplateBindingContext context)
{
if (ambientValues == null)
{
return;
}
foreach (var kvp in ambientValues)
{
if (IsRoutePartNonEmpty(kvp.Value))
{
var parameter = GetParameter(kvp.Key);
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
{
combinedValues.Add(kvp.Key, kvp.Value);
}
}
}
}
[DebuggerDisplay("{DebuggerToString(),nq}")]
private struct TemplateBindingContext
{
@ -444,8 +546,7 @@ namespace Microsoft.AspNetCore.Routing.Template
{
Debug.Assert(!_acceptedValues.ContainsKey(key));
object value;
if (_defaults != null && _defaults.TryGetValue(key, out value))
if (_defaults != null && _defaults.TryGetValue(key, out var value))
{
_acceptedValues.Add(key, value);
}

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.EndpointFinders;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matchers;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_Success()
{
// Arrange
var endpoint = CreateEndpoint("{controller}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" });
@ -47,7 +46,7 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var expectedMessage = "Could not find a matching endpoint to generate a link.";
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" });
@ -67,7 +66,7 @@ namespace Microsoft.AspNetCore.Routing
public void TryGetLink_Fail()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" });
@ -90,9 +89,9 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_MultipleEndpoints_Success()
{
// Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id?}");
var endpoint2 = CreateEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}");
var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id?}");
var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home", action = "Index", id = "10" });
@ -113,9 +112,9 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_MultipleEndpoints_Success2()
{
// Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id}");
var endpoint2 = CreateEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}");
var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id}");
var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home", action = "Index" });
@ -136,10 +135,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_EncodesValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { name = "name with %special #characters" },
explicitValues: new { name = "name with %special #characters" },
ambientValues: new { controller = "Home", action = "Index" });
// Act
@ -159,7 +158,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForListOfStrings()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
new { color = new List<string> { "red", "green", "blue" } },
@ -182,7 +181,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForListOfInts()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
new { items = new List<int> { 10, 20, 30 } },
@ -205,7 +204,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForList_Empty()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
new { color = new List<string> { } },
@ -228,7 +227,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForList_StringWorkaround()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" },
@ -251,10 +250,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_Success_AmbientValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" },
explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" });
// Act
@ -274,10 +273,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseUrl_SetOnRouteOptions()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" },
explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" });
// Act
@ -297,11 +296,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryString_SetOnRouteOptions()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" });
// Act
@ -321,11 +320,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryString_OnlyIfLowercaseUrlIsTrue_SetOnRouteOptions()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" });
// Act
@ -345,10 +344,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_AppendsTrailingSlash_SetOnRouteOptions()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" },
explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" });
// Act
@ -368,11 +367,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" });
// Act
@ -392,10 +391,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "InDex" },
explicitValues: new { action = "InDex" },
ambientValues: new { controller = "HoMe" });
// Act
@ -416,10 +415,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = false });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "InDex" },
explicitValues: new { action = "InDex" },
ambientValues: new { controller = "HoMe" });
// Act
@ -440,11 +439,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" });
// Act
@ -466,11 +465,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" });
// Act
@ -492,10 +491,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = false });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" },
explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" });
// Act
@ -519,9 +518,9 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}",
new { p2 = "catchall" },
defaults: new { p2 = "catchall" },
constraints: new { p2 = "\\d{4}" });
// Act
@ -546,10 +545,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}",
new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}"), });
defaults: new { p2 = "catchall" },
constraints: new { p2 = new RegexRouteConstraint("\\d{4}"), });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -574,10 +573,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{*p2}",
new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}") });
defaults: new { p2 = "catchall" },
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -601,10 +600,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{*p2}",
new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}") });
defaults: new { p2 = "catchall" },
constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -640,7 +639,7 @@ namespace Microsoft.AspNetCore.Routing
.Returns(true)
.Verifiable();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}",
defaults: new { p2 = "catchall" },
constraints: new { p2 = target.Object });
@ -670,13 +669,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store" },
constraints: new { c = constraint });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" },
explicitValues: new { action = "Store" },
ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
var expectedValues = new RouteValueDictionary(
@ -707,13 +706,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store", otherthing = "17" },
constraints: new { c = constraint });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" },
explicitValues: new { action = "Store" },
ambientValues: new { controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary(
@ -741,13 +740,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/{controller}/{action}",
defaults: new { action = "Index" },
constraints: new { c = constraint, });
var context = CreateRouteValuesContext(
suppliedValues: new { controller = "Shopping" },
explicitValues: new { controller = "Shopping" },
ambientValues: new { controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary(
@ -776,13 +775,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
constraints: new { c = constraint, });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store", thirdthing = "13" },
explicitValues: new { action = "Store", thirdthing = "13" },
ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
var expectedValues = new RouteValueDictionary(
@ -808,13 +807,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 4 });
explicitValues: new { action = "Index", controller = "Home", id = 4 });
// Act
var link = linkGenerator.GetLink(
@ -835,13 +834,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -863,12 +862,12 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int?}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 98 });
explicitValues: new { action = "Index", controller = "Home", id = 98 });
// Act
var link = linkGenerator.GetLink(
@ -889,13 +888,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
explicitValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(
@ -916,13 +915,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -944,13 +943,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int:range(1,20)}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 14 });
explicitValues: new { action = "Index", controller = "Home", id = 14 });
// Act
var link = linkGenerator.GetLink(
@ -971,13 +970,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int:range(1,20)}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 50 });
explicitValues: new { action = "Index", controller = "Home", id = 50 });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
@ -1000,13 +999,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var constraint = new MaxLengthRouteConstraint(20);
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{name}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { name = constraint });
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(
@ -1026,10 +1025,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterPresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(
@ -1048,10 +1047,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterNotPresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
explicitValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(
@ -1070,12 +1069,12 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults()
{
// Arrange
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/{name}",
defaults: new { name = "default-products" });
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(
@ -1094,12 +1093,12 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
{
// Arrange
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/{name}",
defaults: new { name = "products" });
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
explicitValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(
@ -1118,10 +1117,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ParameterNotPresentInTemplate_PresentInValues()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products", format = "json" });
explicitValues: new { action = "Index", controller = "Home", name = "products", format = "json" });
// Act
var link = linkGenerator.GetLink(
@ -1141,11 +1140,11 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint(
var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/.{name?}");
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" });
explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act
var link = linkGenerator.GetLink(
@ -1166,10 +1165,10 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint("{controller}/{action}/.{name?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/.{name?}");
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
explicitValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(
@ -1189,10 +1188,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_InSimpleSegment()
{
// Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" });
explicitValues: new { action = "Index", controller = "Home" });
// Act
var link = linkGenerator.GetLink(
@ -1211,10 +1210,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues()
{
// Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { },
explicitValues: new { },
ambientValues: new { c = "17" });
// Act
@ -1234,10 +1233,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues()
{
// Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { },
explicitValues: new { },
ambientValues: new { c = "17" });
// Act
@ -1257,10 +1256,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
{
// Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}");
var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}");
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
suppliedValues: new { },
explicitValues: new { },
ambientValues: new { d = "17" });
// Act
@ -1276,28 +1275,292 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal("/a", link);
}
private RouteValuesBasedEndpointFinderContext CreateRouteValuesContext(object suppliedValues, object ambientValues = null)
public static TheoryData<object, object, object, object> DoesNotDiscardAmbientValuesData
{
var context = new RouteValuesBasedEndpointFinderContext();
context.ExplicitValues = new RouteValueDictionary(suppliedValues);
context.AmbientValues = new RouteValueDictionary(ambientValues);
return context;
get
{
// - ambient values
// - explicit values
// - required values
// - defaults
return new TheoryData<object, object, object, object>
{
// link to same action on same controller
{
new { controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }
},
// link to same action on same controller - ignoring case
{
new { controller = "ProDUcts", action = "EDit", id = 10 },
new { controller = "ProDUcts", action = "EDit" },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }
},
// link to same action and same controller on same area
{
new { area = "Admin", controller = "Products", action = "Edit", id = 10 },
new { area = "Admin", controller = "Products", action = "Edit" },
new { area = "Admin", controller = "Products", action = "Edit", page = (string)null },
new { area = "Admin", controller = "Products", action = "Edit", page = (string)null }
},
// link to same action and same controller on same area
{
new { area = "Admin", controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = "Admin", controller = "Products", action = "Edit", page = (string)null },
new { area = "Admin", controller = "Products", action = "Edit", page = (string)null }
},
// link to same action and same controller
{
new { controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }
},
{
new { controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }
},
{
new { controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = "", controller = "Products", action = "Edit", page = "" },
new { area = "", controller = "Products", action = "Edit", page = "" }
},
// link to same page
{
new { page = "Products/Edit", id = 10 },
new { page = "Products/Edit" },
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" },
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }
},
};
}
}
private MatcherEndpoint CreateEndpoint(
string template,
object defaults = null,
object constraints = null,
int order = 0,
EndpointMetadataCollection metadata = null)
[Theory]
[MemberData(nameof(DoesNotDiscardAmbientValuesData))]
public void TryGetLink_DoesNotDiscardAmbientValues_IfAllRequiredKeysMatch(
object ambientValues,
object explicitValues,
object requiredValues,
object defaults)
{
return new MatcherEndpoint(
MatcherEndpoint.EmptyInvoker,
RoutePatternFactory.Parse(template, defaults, constraints),
new RouteValueDictionary(),
order,
metadata,
null);
// Arrange
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"Products/Edit/{id}",
requiredValues: requiredValues,
defaults: defaults);
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
explicitValues: explicitValues,
ambientValues: ambientValues);
// Act
var canGenerateLink = linkGenerator.TryGetLink(
new LinkGeneratorContext
{
Endpoints = new[] { endpoint },
ExplicitValues = context.ExplicitValues,
AmbientValues = context.AmbientValues
},
out var link);
// Assert
Assert.True(canGenerateLink);
Assert.Equal("/Products/Edit/10", link);
}
[Fact]
public void TryGetLink_DoesNotDiscardAmbientValues_IfAllRequiredValuesMatch_ForGenericKeys()
{
// Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
// Arrange
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"Products/Edit/{id}",
requiredValues: new { c = "Products", a = "Edit" },
defaults: new { c = "Products", a = "Edit" });
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
explicitValues: new { c = "Products", a = "Edit" },
ambientValues: new { c = "Products", a = "Edit", id = 10 });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
new LinkGeneratorContext
{
Endpoints = new[] { endpoint },
ExplicitValues = context.ExplicitValues,
AmbientValues = context.AmbientValues
},
out var link);
// Assert
Assert.True(canGenerateLink);
Assert.Equal("/Products/Edit/10", link);
}
[Fact]
public void TryGetLink_DiscardsAmbientValues_ForGenericKeys()
{
// Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
// Arrange
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"Products/Edit/{id}",
requiredValues: new { c = "Products", a = "Edit" },
defaults: new { c = "Products", a = "Edit" });
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
explicitValues: new { c = "Products", a = "List" },
ambientValues: new { c = "Products", a = "Edit", id = 10 });
// Act
var canGenerateLink = linkGenerator.TryGetLink(
new LinkGeneratorContext
{
Endpoints = new[] { endpoint },
ExplicitValues = context.ExplicitValues,
AmbientValues = context.AmbientValues
},
out var link);
// Assert
Assert.False(canGenerateLink);
Assert.Null(link);
}
public static TheoryData<object, object, object, object> DiscardAmbientValuesData
{
get
{
// - ambient values
// - explicit values
// - required values
// - defaults
return new TheoryData<object, object, object, object>
{
// link to different action on same controller
{
new { controller = "Products", action = "Edit", id = 10 },
new { controller = "Products", action = "List" },
new { area = (string)null, controller = "Products", action = "List", page = (string)null },
new { area = (string)null, controller = "Products", action = "List", page = (string)null }
},
// link to different action on same controller and same area
{
new { area = "Customer", controller = "Products", action = "Edit", id = 10 },
new { area = "Customer", controller = "Products", action = "List" },
new { area = "Customer", controller = "Products", action = "List", page = (string)null },
new { area = "Customer", controller = "Products", action = "List", page = (string)null }
},
// link from one area to a different one
{
new { area = "Admin", controller = "Products", action = "Edit", id = 10 },
new { area = "Consumer", controller = "Products", action = "Edit" },
new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null },
new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null }
},
// link from non-area to a area one
{
new { controller = "Products", action = "Edit", id = 10 },
new { area = "Consumer", controller = "Products", action = "Edit" },
new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null },
new { area = "Consumer", controller = "Products", action = "Edit", page = (string)null }
},
// link from area to a non-area based action
{
new { area = "Admin", controller = "Products", action = "Edit", id = 10 },
new { area = "", controller = "Products", action = "Edit" },
new { area = "", controller = "Products", action = "Edit", page = (string)null },
new { area = "", controller = "Products", action = "Edit", page = (string)null }
},
// link from controller-action to a page
{
new { controller = "Products", action = "Edit", id = 10 },
new { page = "Products/Edit" },
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit"},
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit"}
},
// link from a page to controller-action
{
new { page = "Products/Edit", id = 10 },
new { controller = "Products", action = "Edit" },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null },
new { area = (string)null, controller = "Products", action = "Edit", page = (string)null }
},
// link from one page to a different page
{
new { page = "Products/Details", id = 10 },
new { page = "Products/Edit" },
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" },
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }
},
};
}
}
[Theory]
[MemberData(nameof(DiscardAmbientValuesData))]
public void TryGetLink_DiscardsAmbientValues_IfAnyAmbientValue_IsDifferentThan_EndpointRequiredValues(
object ambientValues,
object explicitValues,
object requiredValues,
object defaults)
{
// Linking to a different action on the same controller
// Arrange
var endpoint = EndpointFactory.CreateMatcherEndpoint(
"Products/Edit/{id}",
requiredValues: requiredValues,
defaults: defaults);
var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(
explicitValues: explicitValues,
ambientValues: ambientValues);
// Act
var canGenerateLink = linkGenerator.TryGetLink(
new LinkGeneratorContext
{
Endpoints = new[] { endpoint },
ExplicitValues = context.ExplicitValues,
AmbientValues = context.AmbientValues
},
out var link);
// Assert
Assert.False(canGenerateLink);
Assert.Null(link);
}
private RouteValuesBasedEndpointFinderContext CreateRouteValuesContext(
object explicitValues,
object ambientValues = null)
{
var context = new RouteValuesBasedEndpointFinderContext();
context.ExplicitValues = new RouteValueDictionary(explicitValues);
context.AmbientValues = new RouteValueDictionary(ambientValues);
return context;
}
private LinkGenerator CreateLinkGenerator(RouteOptions routeOptions = null)

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 Microsoft.AspNetCore.Routing.Matchers;
using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing
{
internal static class EndpointFactory
{
public static MatcherEndpoint CreateMatcherEndpoint(
string template,
object defaults = null,
object constraints = null,
object requiredValues = null,
int order = 0,
string displayName = null,
params object[] metadata)
{
var metadataCollection = EndpointMetadataCollection.Empty;
if (metadata != null)
{
metadataCollection = new EndpointMetadataCollection(metadata);
}
return new MatcherEndpoint(
MatcherEndpoint.EmptyInvoker,
RoutePatternFactory.Parse(template, defaults, constraints),
new RouteValueDictionary(requiredValues),
order,
metadataCollection,
displayName);
}
}
}