Merge branch 'merge/release/2.2-to-master'

# Conflicts:
#	build/dependencies.props
#	korebuild-lock.txt
This commit is contained in:
Kiran Challa 2018-07-31 15:31:33 -07:00
commit 057c00082e
5 changed files with 680 additions and 259 deletions

View File

@ -127,11 +127,16 @@ namespace Microsoft.AspNetCore.Routing
var template = matcherEndpoint.RoutePattern.RawText; var template = matcherEndpoint.RoutePattern.RawText;
template = string.IsNullOrEmpty(template) ? "\"\"" : template; template = string.IsNullOrEmpty(template) ? "\"\"" : template;
sb.Append(template); sb.Append(template);
var requiredValues = matcherEndpoint.RequiredValues.Select(kvp => $"{kvp.Key} = \"{kvp.Value ?? "null"}\"");
sb.Append(", Required Values: new { "); sb.Append(", Required Values: new { ");
sb.Append(string.Join(", ", requiredValues)); sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RequiredValues)));
sb.Append(" }"); sb.Append(" }");
sb.Append(", Order:"); sb.Append(", Defaults: new { ");
sb.Append(string.Join(", ", FormatValues(matcherEndpoint.RoutePattern.Defaults)));
sb.Append(" }");
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
sb.Append(", Route Name: ");
sb.Append(routeNameMetadata?.Name);
sb.Append(", Order: ");
sb.Append(matcherEndpoint.Order); sb.Append(matcherEndpoint.Order);
var httpMethodMetadata = matcherEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>(); var httpMethodMetadata = matcherEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
@ -140,7 +145,8 @@ namespace Microsoft.AspNetCore.Routing
sb.Append(", Http Methods: "); sb.Append(", Http Methods: ");
sb.Append(string.Join(", ", httpMethodMetadata.HttpMethods)); sb.Append(string.Join(", ", httpMethodMetadata.HttpMethods));
} }
sb.Append(", Display Name: ");
sb.Append(matcherEndpoint.DisplayName);
sb.AppendLine(); sb.AppendLine();
} }
else else
@ -150,6 +156,20 @@ namespace Microsoft.AspNetCore.Routing
} }
} }
return sb.ToString(); return sb.ToString();
IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object>> values)
{
return values.Select(
kvp =>
{
var value = "null";
if (kvp.Value != null)
{
value = "\"" + kvp.Value.ToString() + "\"";
}
return kvp.Key + " = " + value;
});
}
} }
} }
} }

View File

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

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text.Encodings.Web; 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 // 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) 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); var context = new TemplateBindingContext(_defaults);
// Find out which entries in the URI are valid for the URI we want to generate. var canCopyParameterAmbientValues = true;
// 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 // In case a route template is not parameterized, we do not know what the required parameters are, so look
// values should then be a="1", b="9", c=<no value>. // at required values of an endpoint to get the key names and then use them to decide if ambient values
// // should be used.
// We also handle the case where a parameter is optional but has no value - we shouldn't // For example, in case of MVC it flattens out a route template like below
// accept additional parameters that appear *after* that parameter. // {controller}/{action}/{id?}
for (var i = 0; i < _template.Parameters.Count; i++) // 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 if (canCopyParameterAmbientValues)
var parameterName = parameter.Name; {
// Copy ambient values when no explicit values are provided
object newParameterValue; CopyParameterAmbientValues(
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue); ambientValues: ambientValues,
explicitValues: explicitValues,
object currentParameterValue = null; context);
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);
}
}
} }
// Add all remaining new values to the list of values we will use for URI generation // Add all remaining new values to the list of values we will use for URI generation
foreach (var kvp in values) CopyNonParamaterExplicitValues(explicitValues, context);
{
if (IsRoutePartNonEmpty(kvp.Value))
{
context.Accept(kvp.Key, kvp.Value);
}
}
// Accept all remaining default values if they match a required parameter // 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]; return null;
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;
}
} }
// Any default values that don't appear as parameters are treated like filters. Any new values // Any default values that don't appear as parameters are treated like filters. Any new values
// provided must match these defaults. // provided must match these defaults.
foreach (var filter in _filters) if (!FiltersMatch(explicitValues))
{ {
var parameter = GetParameter(filter.Key); return null;
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;
}
}
} }
// Add any ambient values that don't match parameters - they need to be visible to constraints // Add any ambient values that don't match parameters - they need to be visible to constraints
// but they will ignored by link generation. // but they will ignored by link generation.
var combinedValues = new RouteValueDictionary(context.AcceptedValues); var combinedValues = new RouteValueDictionary(context.AcceptedValues);
if (ambientValues != null) CopyNonParameterAmbientValues(
{ ambientValues: ambientValues,
foreach (var kvp in ambientValues) combinedValues: combinedValues,
{ context);
if (IsRoutePartNonEmpty(kvp.Value))
{
var parameter = GetParameter(kvp.Key);
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
{
combinedValues.Add(kvp.Key, kvp.Value);
}
}
}
}
return new TemplateValuesResult() return new TemplateValuesResult()
{ {
@ -255,21 +170,18 @@ namespace Microsoft.AspNetCore.Routing.Template
else if (part.IsParameter) else if (part.IsParameter)
{ {
// If it's a parameter, get its value // If it's a parameter, get its value
object value; var hasValue = acceptedValues.TryGetValue(part.Name, out var value);
var hasValue = acceptedValues.TryGetValue(part.Name, out value);
if (hasValue) if (hasValue)
{ {
acceptedValues.Remove(part.Name); acceptedValues.Remove(part.Name);
} }
var isSameAsDefault = false; var isSameAsDefault = false;
object defaultValue; if (_defaults != null &&
if (_defaults != null && _defaults.TryGetValue(part.Name, out defaultValue)) _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); 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}")] [DebuggerDisplay("{DebuggerToString(),nq}")]
private struct TemplateBindingContext private struct TemplateBindingContext
{ {
@ -444,8 +546,7 @@ namespace Microsoft.AspNetCore.Routing.Template
{ {
Debug.Assert(!_acceptedValues.ContainsKey(key)); Debug.Assert(!_acceptedValues.ContainsKey(key));
object value; if (_defaults != null && _defaults.TryGetValue(key, out var value))
if (_defaults != null && _defaults.TryGetValue(key, out value))
{ {
_acceptedValues.Add(key, 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.EndpointFinders;
using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matchers; using Microsoft.AspNetCore.Routing.Matchers;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_Success() public void GetLink_Success()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" }); var context = CreateRouteValuesContext(new { controller = "Home" });
@ -47,7 +46,7 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var expectedMessage = "Could not find a matching endpoint to generate a link."; 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 linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" }); var context = CreateRouteValuesContext(new { controller = "Home" });
@ -67,7 +66,7 @@ namespace Microsoft.AspNetCore.Routing
public void TryGetLink_Fail() public void TryGetLink_Fail()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home" }); var context = CreateRouteValuesContext(new { controller = "Home" });
@ -90,9 +89,9 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_MultipleEndpoints_Success() public void GetLink_MultipleEndpoints_Success()
{ {
// Arrange // Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id?}"); var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id?}");
var endpoint2 = CreateEndpoint("{controller}/{action}"); var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}"); var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home", action = "Index", id = "10" }); var context = CreateRouteValuesContext(new { controller = "Home", action = "Index", id = "10" });
@ -113,9 +112,9 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_MultipleEndpoints_Success2() public void GetLink_MultipleEndpoints_Success2()
{ {
// Arrange // Arrange
var endpoint1 = CreateEndpoint("{controller}/{action}/{id}"); var endpoint1 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{id}");
var endpoint2 = CreateEndpoint("{controller}/{action}"); var endpoint2 = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var endpoint3 = CreateEndpoint("{controller}"); var endpoint3 = EndpointFactory.CreateMatcherEndpoint("{controller}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext(new { controller = "Home", action = "Index" }); var context = CreateRouteValuesContext(new { controller = "Home", action = "Index" });
@ -136,10 +135,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_EncodesValues() public void GetLink_EncodesValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { name = "name with %special #characters" }, explicitValues: new { name = "name with %special #characters" },
ambientValues: new { controller = "Home", action = "Index" }); ambientValues: new { controller = "Home", action = "Index" });
// Act // Act
@ -159,7 +158,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForListOfStrings() public void GetLink_ForListOfStrings()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
new { color = new List<string> { "red", "green", "blue" } }, new { color = new List<string> { "red", "green", "blue" } },
@ -182,7 +181,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForListOfInts() public void GetLink_ForListOfInts()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
new { items = new List<int> { 10, 20, 30 } }, new { items = new List<int> { 10, 20, 30 } },
@ -205,7 +204,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForList_Empty() public void GetLink_ForList_Empty()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
new { color = new List<string> { } }, new { color = new List<string> { } },
@ -228,7 +227,7 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ForList_StringWorkaround() public void GetLink_ForList_StringWorkaround()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" }, 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() public void GetLink_Success_AmbientValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" }, explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -274,10 +273,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseUrl_SetOnRouteOptions() public void GetLink_GeneratesLowercaseUrl_SetOnRouteOptions()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }); var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" }, explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -297,11 +296,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryString_SetOnRouteOptions() public void GetLink_GeneratesLowercaseQueryString_SetOnRouteOptions()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }); new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -321,11 +320,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryString_OnlyIfLowercaseUrlIsTrue_SetOnRouteOptions() public void GetLink_GeneratesLowercaseQueryString_OnlyIfLowercaseUrlIsTrue_SetOnRouteOptions()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = true }); new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -345,10 +344,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_AppendsTrailingSlash_SetOnRouteOptions() public void GetLink_AppendsTrailingSlash_SetOnRouteOptions()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = true }); var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" }, explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -368,11 +367,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions() public void GetLink_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true }); new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -392,10 +391,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue() public void GetLink_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }); var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "InDex" }, explicitValues: new { action = "InDex" },
ambientValues: new { controller = "HoMe" }); ambientValues: new { controller = "HoMe" });
// Act // Act
@ -416,10 +415,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue() public void GetLink_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = false }); var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = false });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "InDex" }, explicitValues: new { action = "InDex" },
ambientValues: new { controller = "HoMe" }); ambientValues: new { controller = "HoMe" });
// Act // Act
@ -440,11 +439,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue() public void GetLink_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }); new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -466,11 +465,11 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue() public void GetLink_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false }); new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }, explicitValues: new { action = "Index", ShowStatus = "True", INFO = "DETAILED" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -492,10 +491,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue() public void GetLink_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = false }); var linkGenerator = CreateLinkGenerator(new RouteOptions() { AppendTrailingSlash = false });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index" }, explicitValues: new { action = "Index" },
ambientValues: new { controller = "Home" }); ambientValues: new { controller = "Home" });
// Act // Act
@ -519,9 +518,9 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "abcd" }); var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}", "{p1}/{p2}",
new { p2 = "catchall" }, defaults: new { p2 = "catchall" },
constraints: new { p2 = "\\d{4}" }); constraints: new { p2 = "\\d{4}" });
// Act // Act
@ -546,10 +545,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}", "{p1}/{p2}",
new { p2 = "catchall" }, defaults: new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}"), }); constraints: new { p2 = new RegexRouteConstraint("\\d{4}"), });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -574,10 +573,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "abcd" }); var context = CreateRouteValuesContext(new { p1 = "abcd" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{*p2}", "{p1}/{*p2}",
new { p2 = "catchall" }, defaults: new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}") }); constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -601,10 +600,10 @@ namespace Microsoft.AspNetCore.Routing
var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" }); var context = CreateRouteValuesContext(new { p1 = "hello", p2 = "1234" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{*p2}", "{p1}/{*p2}",
new { p2 = "catchall" }, defaults: new { p2 = "catchall" },
new { p2 = new RegexRouteConstraint("\\d{4}") }); constraints: new { p2 = new RegexRouteConstraint("\\d{4}") });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -640,7 +639,7 @@ namespace Microsoft.AspNetCore.Routing
.Returns(true) .Returns(true)
.Verifiable(); .Verifiable();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
"{p1}/{p2}", "{p1}/{p2}",
defaults: new { p2 = "catchall" }, defaults: new { p2 = "catchall" },
constraints: new { p2 = target.Object }); constraints: new { p2 = target.Object });
@ -670,13 +669,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var constraint = new CapturingConstraint(); var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store", template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store" }, defaults: new { controller = "Home", action = "Store" },
constraints: new { c = constraint }); constraints: new { c = constraint });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" }, explicitValues: new { action = "Store" },
ambientValues: new { controller = "Home", action = "Blog", extra = "42" }); ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
var expectedValues = new RouteValueDictionary( var expectedValues = new RouteValueDictionary(
@ -707,13 +706,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var constraint = new CapturingConstraint(); var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store", template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store", otherthing = "17" }, defaults: new { controller = "Home", action = "Store", otherthing = "17" },
constraints: new { c = constraint }); constraints: new { c = constraint });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store" }, explicitValues: new { action = "Store" },
ambientValues: new { controller = "Home", action = "Blog" }); ambientValues: new { controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary( var expectedValues = new RouteValueDictionary(
@ -741,13 +740,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var constraint = new CapturingConstraint(); var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/{controller}/{action}", template: "slug/{controller}/{action}",
defaults: new { action = "Index" }, defaults: new { action = "Index" },
constraints: new { c = constraint, }); constraints: new { c = constraint, });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { controller = "Shopping" }, explicitValues: new { controller = "Shopping" },
ambientValues: new { controller = "Home", action = "Blog" }); ambientValues: new { controller = "Home", action = "Blog" });
var expectedValues = new RouteValueDictionary( var expectedValues = new RouteValueDictionary(
@ -776,13 +775,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var constraint = new CapturingConstraint(); var constraint = new CapturingConstraint();
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "slug/Home/Store", template: "slug/Home/Store",
defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" }, defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
constraints: new { c = constraint, }); constraints: new { c = constraint, });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Store", thirdthing = "13" }, explicitValues: new { action = "Store", thirdthing = "13" },
ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" }); ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
var expectedValues = new RouteValueDictionary( var expectedValues = new RouteValueDictionary(
@ -808,13 +807,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int}", template: "Home/Index/{id:int}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { }); constraints: new { });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 4 }); explicitValues: new { action = "Index", controller = "Home", id = 4 });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -835,13 +834,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id}", template: "Home/Index/{id}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" }); constraints: new { id = "int" });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -863,12 +862,12 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int?}", template: "Home/Index/{id:int?}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { }); constraints: new { });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 98 }); explicitValues: new { action = "Index", controller = "Home", id = 98 });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -889,13 +888,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id?}", template: "Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" }); constraints: new { id = "int" });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" }); explicitValues: new { action = "Index", controller = "Home" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -916,13 +915,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id?}", template: "Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { id = "int" }); constraints: new { id = "int" });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = "not-an-integer" }); explicitValues: new { action = "Index", controller = "Home", id = "not-an-integer" });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -944,13 +943,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int:range(1,20)}", template: "Home/Index/{id:int:range(1,20)}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { }); constraints: new { });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 14 }); explicitValues: new { action = "Index", controller = "Home", id = 14 });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -971,13 +970,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{id:int:range(1,20)}", template: "Home/Index/{id:int:range(1,20)}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { }); constraints: new { });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", id = 50 }); explicitValues: new { action = "Index", controller = "Home", id = 50 });
// Act // Act
var canGenerateLink = linkGenerator.TryGetLink( var canGenerateLink = linkGenerator.TryGetLink(
@ -1000,13 +999,13 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var constraint = new MaxLengthRouteConstraint(20); var constraint = new MaxLengthRouteConstraint(20);
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "Home/Index/{name}", template: "Home/Index/{name}",
defaults: new { controller = "Home", action = "Index" }, defaults: new { controller = "Home", action = "Index" },
constraints: new { name = constraint }); constraints: new { name = constraint });
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" }); explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1026,10 +1025,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterPresentInValues() public void GetLink_OptionalParameter_ParameterPresentInValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" }); explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1048,10 +1047,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterNotPresentInValues() public void GetLink_OptionalParameter_ParameterNotPresentInValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" }); explicitValues: new { action = "Index", controller = "Home" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1070,12 +1069,12 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults() public void GetLink_OptionalParameter_ParameterPresentInValuesAndDefaults()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/{name}", template: "{controller}/{action}/{name}",
defaults: new { name = "default-products" }); defaults: new { name = "default-products" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" }); explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1094,12 +1093,12 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults() public void GetLink_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/{name}", template: "{controller}/{action}/{name}",
defaults: new { name = "products" }); defaults: new { name = "products" });
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" }); explicitValues: new { action = "Index", controller = "Home" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1118,10 +1117,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ParameterNotPresentInTemplate_PresentInValues() public void GetLink_ParameterNotPresentInTemplate_PresentInValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products", format = "json" }); explicitValues: new { action = "Index", controller = "Home", name = "products", format = "json" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1141,11 +1140,11 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint( var endpoint = EndpointFactory.CreateMatcherEndpoint(
template: "{controller}/{action}/.{name?}"); template: "{controller}/{action}/.{name?}");
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home", name = "products" }); explicitValues: new { action = "Index", controller = "Home", name = "products" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1166,10 +1165,10 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var endpoint = CreateEndpoint("{controller}/{action}/.{name?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/.{name?}");
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" }); explicitValues: new { action = "Index", controller = "Home" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1189,10 +1188,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameter_InSimpleSegment() public void GetLink_OptionalParameter_InSimpleSegment()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("{controller}/{action}/{name?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("{controller}/{action}/{name?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { action = "Index", controller = "Home" }); explicitValues: new { action = "Index", controller = "Home" });
// Act // Act
var link = linkGenerator.GetLink( var link = linkGenerator.GetLink(
@ -1211,10 +1210,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues() public void GetLink_TwoOptionalParameters_OneValueFromAmbientValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { }, explicitValues: new { },
ambientValues: new { c = "17" }); ambientValues: new { c = "17" });
// Act // Act
@ -1234,10 +1233,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues() public void GetLink_OptionalParameterAfterDefault_OneValueFromAmbientValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { }, explicitValues: new { },
ambientValues: new { c = "17" }); ambientValues: new { c = "17" });
// Act // Act
@ -1257,10 +1256,10 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues() public void GetLink_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
{ {
// Arrange // Arrange
var endpoint = CreateEndpoint("a/{b=15}/{c?}/{d?}"); var endpoint = EndpointFactory.CreateMatcherEndpoint("a/{b=15}/{c?}/{d?}");
var linkGenerator = CreateLinkGenerator(); var linkGenerator = CreateLinkGenerator();
var context = CreateRouteValuesContext( var context = CreateRouteValuesContext(
suppliedValues: new { }, explicitValues: new { },
ambientValues: new { d = "17" }); ambientValues: new { d = "17" });
// Act // Act
@ -1276,28 +1275,292 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal("/a", link); Assert.Equal("/a", link);
} }
private RouteValuesBasedEndpointFinderContext CreateRouteValuesContext(object suppliedValues, object ambientValues = null) public static TheoryData<object, object, object, object> DoesNotDiscardAmbientValuesData
{ {
var context = new RouteValuesBasedEndpointFinderContext(); get
context.ExplicitValues = new RouteValueDictionary(suppliedValues); {
context.AmbientValues = new RouteValueDictionary(ambientValues); // - ambient values
return context; // - 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( [Theory]
string template, [MemberData(nameof(DoesNotDiscardAmbientValuesData))]
object defaults = null, public void TryGetLink_DoesNotDiscardAmbientValues_IfAllRequiredKeysMatch(
object constraints = null, object ambientValues,
int order = 0, object explicitValues,
EndpointMetadataCollection metadata = null) object requiredValues,
object defaults)
{ {
return new MatcherEndpoint( // Arrange
MatcherEndpoint.EmptyInvoker, var endpoint = EndpointFactory.CreateMatcherEndpoint(
RoutePatternFactory.Parse(template, defaults, constraints), "Products/Edit/{id}",
new RouteValueDictionary(), requiredValues: requiredValues,
order, defaults: defaults);
metadata, var linkGenerator = CreateLinkGenerator();
null); 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) 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);
}
}
}