diff --git a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs
index efdb91d2f6..93e78e9094 100644
--- a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs
+++ b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs
@@ -58,6 +58,8 @@ namespace Microsoft.AspNetCore.Mvc.Performance
{
var endpointDataSource = CreateMvcEndpointDataSource(_attributeActionProvider);
var endpoints = endpointDataSource.Endpoints;
+
+ AssertHasEndpoints(endpoints);
}
[Benchmark]
@@ -66,6 +68,8 @@ namespace Microsoft.AspNetCore.Mvc.Performance
var endpointDataSource = CreateMvcEndpointDataSource(_conventionalActionProvider);
endpointDataSource.ConventionalEndpointInfos.AddRange(_conventionalEndpointInfos);
var endpoints = endpointDataSource.Endpoints;
+
+ AssertHasEndpoints(endpoints);
}
private ActionDescriptor CreateAttributeRoutedAction(int id)
@@ -110,11 +114,20 @@ namespace Microsoft.AspNetCore.Mvc.Performance
var dataSource = new MvcEndpointDataSource(
actionDescriptorCollectionProvider,
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())),
- new MockParameterPolicyFactory());
+ new MockParameterPolicyFactory(),
+ new MockRoutePatternTransformer());
return dataSource;
}
+ private class MockRoutePatternTransformer : RoutePatternTransformer
+ {
+ public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
+ {
+ return original;
+ }
+ }
+
private class MockActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
{
public MockActionDescriptorCollectionProvider(List actionDescriptors)
@@ -137,5 +150,13 @@ namespace Microsoft.AspNetCore.Mvc.Performance
throw new NotImplementedException();
}
}
+
+ private static void AssertHasEndpoints(IReadOnlyList endpoints)
+ {
+ if (endpoints.Count == 0)
+ {
+ throw new InvalidOperationException("Expected endpoints from data source.");
+ }
+ }
}
}
diff --git a/src/Mvc/samples/MvcSandbox/SlugifyParameterTransformer.cs b/src/Mvc/samples/MvcSandbox/SlugifyParameterTransformer.cs
new file mode 100644
index 0000000000..7c5b8c396f
--- /dev/null
+++ b/src/Mvc/samples/MvcSandbox/SlugifyParameterTransformer.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text.RegularExpressions;
+using Microsoft.AspNetCore.Routing;
+
+namespace MvcSandbox
+{
+ public class SlugifyParameterTransformer : IOutboundParameterTransformer
+ {
+ public string TransformOutbound(object value)
+ {
+ // Slugify value
+ return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLower();
+ }
+ }
+}
+
diff --git a/src/Mvc/samples/MvcSandbox/Startup.cs b/src/Mvc/samples/MvcSandbox/Startup.cs
index dac9f9d057..58efaf78f0 100644
--- a/src/Mvc/samples/MvcSandbox/Startup.cs
+++ b/src/Mvc/samples/MvcSandbox/Startup.cs
@@ -5,11 +5,13 @@ using System;
using System.IO;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -20,6 +22,10 @@ namespace MvcSandbox
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
+ services.AddRouting(options =>
+ {
+ options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Latest);
}
@@ -37,6 +43,27 @@ namespace MvcSandbox
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
+ builder.MapControllerRoute(
+ name: "transform",
+ template: "Transform/{controller:slugify=Home}/{action:slugify=Index}/{id?}",
+ defaults: null,
+ constraints: new { controller = "Home" });
+
+ builder.MapGet(
+ "/graph",
+ "DFA Graph",
+ (httpContext) =>
+ {
+ using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
+ {
+ var graphWriter = httpContext.RequestServices.GetRequiredService();
+ var dataSource = httpContext.RequestServices.GetRequiredService();
+ graphWriter.Write(dataSource, writer);
+ }
+
+ return Task.CompletedTask;
+ });
+
builder.MapApplication();
builder.MapHealthChecks("/healthz");
@@ -50,7 +77,7 @@ namespace MvcSandbox
private static Task WriteEndpoints(HttpContext httpContext)
{
- var dataSource = httpContext.RequestServices.GetRequiredService();
+ var dataSource = httpContext.RequestServices.GetRequiredService();
var sb = new StringBuilder();
sb.AppendLine("Endpoints:");
@@ -81,6 +108,7 @@ namespace MvcSandbox
factory
.AddConsole()
.AddDebug();
+ factory.SetMinimumLevel(LogLevel.Trace);
})
.UseIISIntegration()
.UseKestrel()
diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/MvcEndpointDataSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/MvcEndpointDataSource.cs
index 53e37a4595..3f1ef4d360 100644
--- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/MvcEndpointDataSource.cs
+++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/MvcEndpointDataSource.cs
@@ -5,13 +5,11 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
-using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
@@ -25,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
private readonly IActionDescriptorCollectionProvider _actions;
private readonly MvcEndpointInvokerFactory _invokerFactory;
private readonly ParameterPolicyFactory _parameterPolicyFactory;
+ private readonly RoutePatternTransformer _routePatternTransformer;
// The following are protected by this lock for WRITES only. This pattern is similar
// to DefaultActionDescriptorChangeProvider - see comments there for details on
@@ -37,26 +36,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
public MvcEndpointDataSource(
IActionDescriptorCollectionProvider actions,
MvcEndpointInvokerFactory invokerFactory,
- ParameterPolicyFactory parameterPolicyFactory)
+ ParameterPolicyFactory parameterPolicyFactory,
+ RoutePatternTransformer routePatternTransformer)
{
- if (actions == null)
- {
- throw new ArgumentNullException(nameof(actions));
- }
-
- if (invokerFactory == null)
- {
- throw new ArgumentNullException(nameof(invokerFactory));
- }
-
- if (parameterPolicyFactory == null)
- {
- throw new ArgumentNullException(nameof(parameterPolicyFactory));
- }
-
_actions = actions;
_invokerFactory = invokerFactory;
_parameterPolicyFactory = parameterPolicyFactory;
+ _routePatternTransformer = routePatternTransformer;
ConventionalEndpointInfos = new List();
AttributeRoutingConventionResolvers = new List>();
@@ -115,7 +101,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
lock (_lock)
{
var endpoints = new List();
- StringBuilder patternStringBuilder = null;
foreach (var action in _actions.ActionDescriptors.Items)
{
@@ -129,48 +114,31 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
var conventionalRouteOrder = 1;
- // Check each of the conventional patterns to see if the action would be reachable
- // If the action and pattern are compatible then create an endpoint with the
- // area/controller/action parameter parts replaced with literals
- //
- // e.g. {controller}/{action} with HomeController.Index and HomeController.Login
- // would result in endpoints:
- // - Home/Index
- // - Home/Login
+ // Check each of the conventional patterns to see if the action would be reachable.
+ // If the action and pattern are compatible then create an endpoint with action
+ // route values on the pattern.
foreach (var endpointInfo in ConventionalEndpointInfos)
{
// An 'endpointInfo' is applicable if:
- // 1. it has a parameter (or default value) for 'required' non-null route value
- // 2. it does not have a parameter (or default value) for 'required' null route value
- var isApplicable = true;
- foreach (var routeKey in action.RouteValues.Keys)
- {
- if (!MatchRouteValue(action, endpointInfo, routeKey))
- {
- isApplicable = false;
- break;
- }
- }
+ // 1. It has a parameter (or default value) for 'required' non-null route value
+ // 2. It does not have a parameter (or default value) for 'required' null route value
+ var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(endpointInfo.ParsedPattern, action.RouteValues);
- if (!isApplicable)
+ if (updatedRoutePattern == null)
{
continue;
}
- conventionalRouteOrder = CreateEndpoints(
- endpoints,
- ref patternStringBuilder,
+ var endpoint = CreateEndpoint(
action,
- conventionalRouteOrder,
- endpointInfo.ParsedPattern,
- endpointInfo.MergedDefaults,
- endpointInfo.Defaults,
+ updatedRoutePattern,
endpointInfo.Name,
+ conventionalRouteOrder++,
endpointInfo.DataTokens,
- endpointInfo.ParameterPolicies,
- suppressLinkGeneration: false,
- suppressPathMatching: false,
+ false,
+ false,
endpointInfo.Conventions);
+ endpoints.Add(endpoint);
}
}
else
@@ -185,20 +153,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
- CreateEndpoints(
- endpoints,
- ref patternStringBuilder,
+ // Modify the route and required values to ensure required values can be successfully subsituted.
+ // Subsitituting required values into an attribute route pattern should always succeed.
+ var (resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern);
+
+ var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues);
+
+ if (updatedRoutePattern == null)
+ {
+ throw new InvalidOperationException("Failed to update route pattern with required values.");
+ }
+
+ var endpoint = CreateEndpoint(
action,
- action.AttributeRouteInfo.Order,
- attributeRoutePattern,
- attributeRoutePattern.Defaults,
- nonInlineDefaults: null,
+ updatedRoutePattern,
action.AttributeRouteInfo.Name,
+ action.AttributeRouteInfo.Order,
dataTokens: null,
- allParameterPolicies: null,
action.AttributeRouteInfo.SuppressLinkGeneration,
action.AttributeRouteInfo.SuppressPathMatching,
conventionBuilder.Conventions);
+ endpoints.Add(endpoint);
}
}
@@ -220,6 +195,58 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}
}
+ private static (RoutePattern resolvedRoutePattern, IDictionary resolvedRequiredValues) ResolveDefaultsAndRequiredValues(ActionDescriptor action, RoutePattern attributeRoutePattern)
+ {
+ RouteValueDictionary updatedDefaults = null;
+ IDictionary resolvedRequiredValues = null;
+
+ foreach (var routeValue in action.RouteValues)
+ {
+ var parameter = attributeRoutePattern.GetParameter(routeValue.Key);
+
+ if (!RouteValueEqualityComparer.Default.Equals(routeValue.Value, string.Empty))
+ {
+ if (parameter == null)
+ {
+ // The attribute route has a required value with no matching parameter
+ // Add the required values without a parameter as a default
+ // e.g.
+ // Template: "Login/{action}"
+ // Required values: { controller = "Login", action = "Index" }
+ // Updated defaults: { controller = "Login" }
+
+ if (updatedDefaults == null)
+ {
+ updatedDefaults = new RouteValueDictionary(attributeRoutePattern.Defaults);
+ }
+
+ updatedDefaults[routeValue.Key] = routeValue.Value;
+ }
+ }
+ else
+ {
+ if (parameter != null)
+ {
+ // The attribute route has a null or empty required value with a matching parameter
+ // Remove the required value from the route
+
+ if (resolvedRequiredValues == null)
+ {
+ resolvedRequiredValues = new Dictionary(action.RouteValues);
+ }
+
+ resolvedRequiredValues.Remove(parameter.Name);
+ }
+ }
+ }
+ if (updatedDefaults != null)
+ {
+ attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template, updatedDefaults, parameterPolicies: null);
+ }
+
+ return (attributeRoutePattern, resolvedRequiredValues ?? action.RouteValues);
+ }
+
private DefaultEndpointConventionBuilder ResolveActionConventionBuilder(ActionDescriptor action)
{
foreach (var filter in AttributeRoutingConventionResolvers)
@@ -234,339 +261,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing
return null;
}
- // CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values
- // Because of default values it is possible for a route pattern to resolve to multiple endpoints
- private int CreateEndpoints(
- List endpoints,
- ref StringBuilder patternStringBuilder,
- ActionDescriptor action,
- int routeOrder,
- RoutePattern routePattern,
- IReadOnlyDictionary allDefaults,
- IReadOnlyDictionary nonInlineDefaults,
- string name,
- RouteValueDictionary dataTokens,
- IDictionary> allParameterPolicies,
- bool suppressLinkGeneration,
- bool suppressPathMatching,
- List> conventions)
- {
- var newPathSegments = routePattern.PathSegments.ToList();
- var hasLinkGenerationEndpoint = false;
-
- // Create a mutable copy
- var nonInlineDefaultsCopy = nonInlineDefaults != null
- ? new RouteValueDictionary(nonInlineDefaults)
- : null;
-
- var resolvedRouteValues = ResolveActionRouteValues(action, allDefaults);
-
- for (var i = 0; i < newPathSegments.Count; i++)
- {
- // Check if the pattern can be shortened because the remaining parameters are optional
- //
- // e.g. Matching pattern {controller=Home}/{action=Index} against HomeController.Index
- // can resolve to the following endpoints: (sorted by RouteEndpoint.Order)
- // - /
- // - /Home
- // - /Home/Index
- if (UseDefaultValuePlusRemainingSegmentsOptional(
- i,
- action,
- resolvedRouteValues,
- allDefaults,
- ref nonInlineDefaultsCopy,
- newPathSegments))
- {
- // The route pattern has matching default values AND an optional parameter
- // For link generation we need to include an endpoint with parameters and default values
- // so the link is correctly shortened
- // e.g. {controller=Home}/{action=Index}/{id=17}
- if (!hasLinkGenerationEndpoint)
- {
- var ep = CreateEndpoint(
- action,
- resolvedRouteValues,
- name,
- GetPattern(ref patternStringBuilder, newPathSegments),
- newPathSegments,
- nonInlineDefaultsCopy,
- routeOrder++,
- dataTokens,
- suppressLinkGeneration,
- true,
- conventions);
- endpoints.Add(ep);
-
- hasLinkGenerationEndpoint = true;
- }
-
- var subPathSegments = newPathSegments.Take(i);
-
- var subEndpoint = CreateEndpoint(
- action,
- resolvedRouteValues,
- name,
- GetPattern(ref patternStringBuilder, subPathSegments),
- subPathSegments,
- nonInlineDefaultsCopy,
- routeOrder++,
- dataTokens,
- suppressLinkGeneration,
- suppressPathMatching,
- conventions);
- endpoints.Add(subEndpoint);
- }
-
- UpdatePathSegments(i, action, resolvedRouteValues, routePattern, newPathSegments, ref allParameterPolicies);
- }
-
- var finalEndpoint = CreateEndpoint(
- action,
- resolvedRouteValues,
- name,
- GetPattern(ref patternStringBuilder, newPathSegments),
- newPathSegments,
- nonInlineDefaultsCopy,
- routeOrder++,
- dataTokens,
- suppressLinkGeneration,
- suppressPathMatching,
- conventions);
- endpoints.Add(finalEndpoint);
-
- return routeOrder;
-
- string GetPattern(ref StringBuilder sb, IEnumerable segments)
- {
- if (sb == null)
- {
- sb = new StringBuilder();
- }
-
- RoutePatternWriter.WriteString(sb, segments);
- var rawPattern = sb.ToString();
- sb.Length = 0;
-
- return rawPattern;
- }
- }
-
- private static IDictionary ResolveActionRouteValues(ActionDescriptor action, IReadOnlyDictionary allDefaults)
- {
- Dictionary resolvedRequiredValues = null;
-
- foreach (var kvp in action.RouteValues)
- {
- // Check whether there is a matching default value with a different case
- // e.g. {controller=HOME}/{action} with HomeController.Index will have route values:
- // - controller = HOME
- // - action = Index
- if (allDefaults.TryGetValue(kvp.Key, out var value) &&
- value is string defaultValue &&
- !string.Equals(kvp.Value, defaultValue, StringComparison.Ordinal) &&
- string.Equals(kvp.Value, defaultValue, StringComparison.OrdinalIgnoreCase))
- {
- if (resolvedRequiredValues == null)
- {
- resolvedRequiredValues = new Dictionary(action.RouteValues, StringComparer.OrdinalIgnoreCase);
- }
-
- resolvedRequiredValues[kvp.Key] = defaultValue;
- }
- }
-
- return resolvedRequiredValues ?? action.RouteValues;
- }
-
- private void UpdatePathSegments(
- int i,
- ActionDescriptor action,
- IDictionary resolvedRequiredValues,
- RoutePattern routePattern,
- List newPathSegments,
- ref IDictionary> allParameterPolicies)
- {
- List segmentParts = null; // Initialize only as needed
- var segment = newPathSegments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
- {
- var part = segment.Parts[j];
-
- if (part is RoutePatternParameterPart parameterPart)
- {
- if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue))
- {
- if (segmentParts == null)
- {
- segmentParts = segment.Parts.ToList();
- }
- if (allParameterPolicies == null)
- {
- allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
- }
-
- // Route value could be null if it is a "known" route value.
- // Do not use the null value to de-normalize the route pattern,
- // instead leave the parameter unchanged.
- // e.g.
- // RouteValues will contain a null "page" value if there are Razor pages
- // Skip replacing the {page} parameter
- if (parameterRouteValue != null)
- {
- if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
- {
- // Check if the parameter has a transformer policy
- // Use the first transformer policy
- for (var k = 0; k < parameterPolicies.Count; k++)
- {
- if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer)
- {
- parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue);
- break;
- }
- }
- }
-
- segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
- }
- }
- }
- }
-
- // A parameter part was replaced so replace segment with updated parts
- if (segmentParts != null)
- {
- newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
- }
-
- }
-
- private bool UseDefaultValuePlusRemainingSegmentsOptional(
- int segmentIndex,
- ActionDescriptor action,
- IDictionary resolvedRequiredValues,
- IReadOnlyDictionary allDefaults,
- ref RouteValueDictionary nonInlineDefaults,
- List pathSegments)
- {
- // Check whether the remaining segments are all optional and one or more of them is
- // for area/controller/action and has a default value
- var usedDefaultValue = false;
-
- for (var i = segmentIndex; i < pathSegments.Count; i++)
- {
- var segment = pathSegments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
- {
- var part = segment.Parts[j];
- if (part.IsParameter && part is RoutePatternParameterPart parameterPart)
- {
- if (allDefaults.TryGetValue(parameterPart.Name, out var v))
- {
- if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var routeValue))
- {
- if (string.Equals(v as string, routeValue, StringComparison.OrdinalIgnoreCase))
- {
- usedDefaultValue = true;
- continue;
- }
- }
- else
- {
- if (nonInlineDefaults == null)
- {
- nonInlineDefaults = new RouteValueDictionary();
- }
- nonInlineDefaults.TryAdd(parameterPart.Name, v);
-
- usedDefaultValue = true;
- continue;
- }
- }
-
- if (parameterPart.IsOptional || parameterPart.IsCatchAll)
- {
- continue;
- }
- }
- else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart
- && separatorPart.Content == ".")
- {
- // Check if this pattern ends in an optional extension, e.g. ".{ext?}"
- // Current literal must be "." and followed by a single optional parameter part
- var nextPartIndex = j + 1;
-
- if (nextPartIndex == segment.Parts.Count - 1
- && segment.Parts[nextPartIndex].IsParameter
- && segment.Parts[nextPartIndex] is RoutePatternParameterPart extensionParameterPart
- && extensionParameterPart.IsOptional)
- {
- continue;
- }
- }
-
- // Stop because there is a non-optional/non-defaulted trailing value
- return false;
- }
- }
-
- return usedDefaultValue;
- }
-
- private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey)
- {
- if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
- {
- // Action does not have a value for this routeKey, most likely because action is not in an area
- // Check that the pattern does not have a parameter for the routeKey
- var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey);
- if (matchingParameter == null &&
- (!endpointInfo.ParsedPattern.Defaults.TryGetValue(routeKey, out var value) ||
- !string.IsNullOrEmpty(Convert.ToString(value))))
- {
- return true;
- }
- }
- else
- {
- if (endpointInfo.MergedDefaults != null && string.Equals(actionValue, endpointInfo.MergedDefaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey);
- if (matchingParameter != null)
- {
- // Check that the value matches against constraints on that parameter
- // e.g. For {controller:regex((Home|Login))} the controller value must match the regex
- if (endpointInfo.ParameterPolicies.TryGetValue(routeKey, out var parameterPolicies))
- {
- foreach (var policy in parameterPolicies)
- {
- if (policy is IRouteConstraint constraint
- && !constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
- {
- // Did not match constraint
- return false;
- }
- }
- }
-
- return true;
- }
- }
-
- return false;
- }
-
private RouteEndpoint CreateEndpoint(
ActionDescriptor action,
- IDictionary actionRouteValues,
+ RoutePattern routePattern,
string routeName,
- string patternRawText,
- IEnumerable segments,
- object nonInlineDefaults,
int order,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration,
@@ -583,16 +281,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing
return invoker.InvokeAsync();
};
- var defaults = new RouteValueDictionary(nonInlineDefaults);
- EnsureRequiredValuesInDefaults(actionRouteValues, defaults, segments);
-
- var model = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments), order);
+ var model = new RouteEndpointModel(requestDelegate, routePattern, order);
AddEndpointMetadata(
model.Metadata,
action,
routeName,
- new RouteValueDictionary(actionRouteValues),
dataTokens,
suppressLinkGeneration,
suppressPathMatching);
@@ -616,7 +310,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
IList metadata,
ActionDescriptor action,
string routeName,
- RouteValueDictionary requiredValues,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration,
bool suppressPathMatching)
@@ -637,7 +330,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
metadata.Add(new DataTokensMetadata(dataTokens));
}
- metadata.Add(new RouteValuesAddressMetadata(routeName, requiredValues));
+ metadata.Add(new RouteNameMetadata(routeName));
// Add filter descriptors to endpoint metadata
if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0)
@@ -685,33 +378,5 @@ namespace Microsoft.AspNetCore.Mvc.Routing
metadata.Add(new SuppressMatchingMetadata());
}
}
-
- // Ensure route values are a subset of defaults
- // Examples:
- //
- // Template: {controller}/{action}/{category}/{id?}
- // Defaults(in-line or non in-line): category=products
- // Required values: controller=foo, action=bar
- // Final constructed pattern: foo/bar/{category}/{id?}
- // Final defaults: controller=foo, action=bar, category=products
- //
- // Template: {controller=Home}/{action=Index}/{category=products}/{id?}
- // Defaults: controller=Home, action=Index, category=products
- // Required values: controller=foo, action=bar
- // Final constructed pattern: foo/bar/{category}/{id?}
- // Final defaults: controller=foo, action=bar, category=products
- private void EnsureRequiredValuesInDefaults(
- IDictionary routeValues,
- RouteValueDictionary defaults,
- IEnumerable segments)
- {
- foreach (var kvp in routeValues)
- {
- if (kvp.Value != null)
- {
- defaults[kvp.Key] = kvp.Value;
- }
- }
- }
}
}
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs
index a07cad51cb..2629884624 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs
@@ -25,11 +25,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var endpoint2 = CreateEndpoint(
"Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -55,11 +55,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var endpoint2 = CreateEndpoint(
"Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -83,11 +83,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var endpoint2 = CreateEndpoint(
"Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -114,11 +114,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var endpoint2 = CreateEndpoint(
"Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -144,11 +144,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var endpoint2 = CreateEndpoint(
"Home/Index/{id?}",
defaults: new { controller = "Home", action = "Index", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
+ requiredValues: new { controller = "Home", action = "Index" });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.Routing
{
return new RouteEndpoint(
(httpContext) => Task.CompletedTask,
- RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
+ RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues),
order,
new EndpointMetadataCollection(metadata ?? Array.Empty()),
null);
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs
index d6931df173..0f85a857fa 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs
@@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
"Home/Index",
defaults: new { controller = "Home", action = "Index" },
requiredValues: new { controller = "Home", action = "Index" },
- metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() }));
+ metadata: new[] { new SuppressLinkGenerationMetadata() });
var urlHelper = CreateUrlHelper(new[] { endpoint });
// Act
@@ -273,19 +273,20 @@ namespace Microsoft.AspNetCore.Mvc.Routing
object requiredValues = null,
int order = 0,
string routeName = null,
- EndpointMetadataCollection metadataCollection = null)
+ IList metadata = null)
{
- if (metadataCollection == null)
+ metadata = metadata ?? new List();
+
+ if (routeName != null)
{
- metadataCollection = new EndpointMetadataCollection(
- new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
+ metadata.Add(new RouteNameMetadata(routeName));
}
return new RouteEndpoint(
(httpContext) => Task.CompletedTask,
- RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
+ RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues),
order,
- metadataCollection,
+ new EndpointMetadataCollection(metadata),
null);
}
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/MvcEndpointDataSourceTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/MvcEndpointDataSourceTests.cs
index 3fcbcc9d1b..d1c923b685 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/MvcEndpointDataSourceTests.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/MvcEndpointDataSourceTests.cs
@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Moq;
@@ -64,14 +65,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType(endpoint);
- var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata();
- Assert.NotNull(routeValuesAddressMetadata);
- var endpointValue = routeValuesAddressMetadata.RequiredValues["Name"];
+ var endpointValue = matcherEndpoint.RoutePattern.RequiredValues["Name"];
Assert.Equal(routeValue, endpointValue);
Assert.Equal(displayName, matcherEndpoint.DisplayName);
Assert.Equal(order, matcherEndpoint.Order);
- Assert.Equal("Template!", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("/Template!", matcherEndpoint.RoutePattern.RawText);
}
[Fact]
@@ -131,164 +130,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
Assert.True(actionInvokerCalled);
}
- public static TheoryData GetSingleActionData_Conventional
- {
- get => GetSingleActionData(true);
- }
-
- public static TheoryData GetSingleActionData_Attribute
- {
- get => GetSingleActionData(false);
- }
-
- private static TheoryData GetSingleActionData(bool isConventionalRouting)
- {
- var data = new TheoryData
- {
- {"{controller}/{action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
- {"{controller}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }},
- {"{action}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }},
- {"{Controller}/{Action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
- {"{Controller}/{Action}/{id?}/{more?}", null, new[] { "TestController/TestAction/{id?}/{more?}" }},
- {"{CONTROLLER}/{ACTION}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
- {"{controller}/{action=TestAction}", "TestController/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }},
- {"{controller}/{action=TestAction}/{id?}", "TestController/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }},
- {"{controller}/{action=TESTACTION}/{id?}", "TestController/{action=TESTACTION}/{id?}", new[] { "TestController", "TestController/TESTACTION/{id?}" }},
- {"{controller}/{action=TestAction}/{id?}/{more}", null, new[] { "TestController/TestAction/{id?}/{more}" }},
- {"{controller=TestController}/{action=TestAction}/{id?}", "{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }},
- {"{controller=TestController}/{action=TestAction}/{id?}/{more?}", "{controller=TestController}/{action=TestAction}/{id?}/{more?}", new[] { "", "TestController", "TestController/TestAction/{id?}/{more?}" }},
- {"{controller}/{action}/{*catchAll}", null, new[] { "TestController/TestAction/{*catchAll}" }},
- {"{controller}/{action=TestAction}/{*catchAll}", "TestController/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }},
- {"{controller}/{action=TestAction}/{id?}/{*catchAll}", "TestController/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }},
- {"{controller}/{action=TestAction}/{id?}/{**catchAll}", "TestController/{action=TestAction}/{id?}/{**catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{**catchAll}" }},
- {"{controller}/{action}.{ext?}", null, new[] { "TestController/TestAction.{ext?}" }},
- {"{controller}/{action=TestAction}.{ext?}", "TestController/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }},
- {"{controller}/{action=TestAction}.{ext?}/{more?}", "TestController/{action=TestAction}.{ext?}/{more?}", new[] { "TestController", "TestController/TestAction.{ext?}/{more?}" }},
- {"{controller}/{action=TestAction}.{ext?}/{more}", null, new[] { "TestController/TestAction.{ext?}/{more}" }},
- {"{controller:upper-case}/{action:upper-case=TestAction}.{ext?}", "TESTCONTROLLER/{action:upper-case=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TESTACTION.{ext?}" }},
- };
-
- return data;
- }
-
- [Theory]
- [MemberData(nameof(GetSingleActionData_Conventional))]
- public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns)
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
-
- // Act
- var endpoints = dataSource.Endpoints.ToList();
-
- // Assert
-
- // Ensure there are no endpoints with duplicate Order values
- Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1);
-
- endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList();
-
- AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
-
- var inspectors = finalEndpointPatterns
- .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText)))
- .ToArray();
-
- // Assert
- Assert.Collection(endpoints, inspectors);
- }
-
- [Theory]
- [MemberData(nameof(GetSingleActionData_Attribute))]
- public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns)
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- attributeRouteTemplate: endpointInfoRoute,
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
-
- // Act
- var endpoints = dataSource.Endpoints.ToList();
-
- // Ensure there are no endpoints with duplicate Order values
- Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1);
-
- endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList();
-
- AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
-
- // Assert
- var inspectors = finalEndpointPatterns
- .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText)))
- .ToArray();
-
- // Assert
- Assert.Collection(endpoints, inspectors);
- }
-
- [Theory]
- [InlineData("{area}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
- [InlineData("{controller}/{action}/{id?}", null, new string[] { })]
- [InlineData("{area=TestArea}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
- [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", "TestArea/TestController/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}"})]
- [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", "{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
- [InlineData("{area:exists}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
- [InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", null, new[] { "TESTAREA/TestController/TestAction/{id?}" })]
- public void Endpoints_AreaSingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointTemplates)
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction", area = "TestArea" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
-
- var services = new ServiceCollection();
- services.AddRouting();
- services.AddSingleton(actionDescriptorCollection);
-
- var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
- services.Configure(routeOptionsSetup.Configure);
- services.Configure(options =>
- {
- options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
- });
-
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider()));
-
- // Act
- var endpoints = dataSource.Endpoints.ToList();
-
- // Assert
-
- // Ensure there are no endpoints with duplicate Order values
- Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1);
-
- endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList();
-
- AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
-
- var inspectors = finalEndpointTemplates
- .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText)))
- .ToArray();
-
- // Assert
- Assert.Collection(endpoints, inspectors);
- }
-
- private static void AssertSuppressMatchingTemplate(string suppressMatchingTemplate, List endpoints)
- {
- if (suppressMatchingTemplate != null)
- {
- var suppressMatchingEndpoint = endpoints.First();
- Assert.True(suppressMatchingEndpoint.Metadata.GetMetadata()?.SuppressMatching);
- Assert.Equal(suppressMatchingTemplate, Assert.IsType(suppressMatchingEndpoint).RoutePattern.RawText);
- endpoints.Remove(suppressMatchingEndpoint);
- }
- }
-
[Fact]
public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue()
{
@@ -321,34 +162,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var endpoints = dataSource.Endpoints;
// Assert
- Assert.Collection(endpoints,
- (e) => Assert.Equal("TestController/TestAction/{page}", Assert.IsType(e).RoutePattern.RawText));
- }
-
- [Fact]
- public void Endpoints_SingleAction_WithActionDefault()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- string.Empty,
- "{controller}/{action}",
- new RouteValueDictionary(new { action = "TestAction" })));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(endpoints,
+ Assert.Collection(endpoints.Cast(),
(e) =>
{
- Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText);
- Assert.True(e.Metadata.GetMetadata().SuppressMatching);
- },
- (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText));
+ Assert.Equal("{controller}/{action}/{page}", e.RoutePattern.RawText);
+ Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("TestAction", e.RoutePattern.RequiredValues["action"]);
+ Assert.False(e.RoutePattern.RequiredValues.ContainsKey("page"));
+ });
}
[Fact]
@@ -375,9 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Assert
Assert.Collection(endpoints1,
- (e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText));
+ (e) => Assert.Equal("{controller}/{action}", Assert.IsType(e).RoutePattern.RawText));
Assert.Same(endpoints1, endpoints2);
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
@@ -417,9 +236,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var endpoints = dataSource.Endpoints;
Assert.Collection(endpoints,
- (e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText));
+ (e) =>
+ {
+ var routePattern = Assert.IsType(e).RoutePattern;
+ Assert.Equal("{controller}/{action}", routePattern.RawText);
+ Assert.Equal("TestController", routePattern.RequiredValues["controller"]);
+ Assert.Equal("TestAction", routePattern.RequiredValues["action"]);
+ });
actionDescriptorCollectionProviderMock
.Setup(m => m.ActionDescriptors)
@@ -435,7 +258,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
Assert.NotSame(endpoints, newEndpoints);
Assert.Collection(newEndpoints,
- (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).RoutePattern.RawText));
+ (e) =>
+ {
+ var routePattern = Assert.IsType(e).RoutePattern;
+ Assert.Equal("{controller}/{action}", routePattern.RawText);
+ Assert.Equal("NewTestController", routePattern.RequiredValues["controller"]);
+ Assert.Equal("NewTestAction", routePattern.RequiredValues["action"]);
+ });
}
[Fact]
@@ -456,37 +285,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var endpoints = dataSource.Endpoints;
// Assert
- Assert.Collection(endpoints,
- (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).RoutePattern.RawText),
- (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).RoutePattern.RawText));
- }
-
- [Theory]
- [InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })]
- [InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })]
- [InlineData("{controller}/{action:regex((TestAction1|TestAction2)):upper-case}", new[] { "TestController1/TESTACTION1", "TestController1/TESTACTION2", "TestController2/TESTACTION1" })]
- public void Endpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates)
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController1", action = "TestAction1" },
- new { controller = "TestController1", action = "TestAction2" },
- new { controller = "TestController1", action = "TestAction3" },
- new { controller = "TestController2", action = "TestAction1" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- string.Empty,
- endpointInfoRoute));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- var inspectors = finalEndpointTemplates
- .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText)))
- .ToArray();
-
- // Assert
- Assert.Collection(endpoints, inspectors);
+ Assert.Collection(endpoints.Cast(),
+ (e) =>
+ {
+ Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
+ Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("TestAction1", e.RoutePattern.RequiredValues["action"]);
+ },
+ (e) =>
+ {
+ Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
+ Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("TestAction2", e.RoutePattern.RequiredValues["action"]);
+ });
}
[Fact]
@@ -505,9 +316,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Assert
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType(endpoint);
- var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata();
- Assert.NotNull(routeValuesAddressNameMetadata);
- Assert.Equal(string.Empty, routeValuesAddressNameMetadata.RouteName);
+ var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata();
+ Assert.NotNull(routeNameMetadata);
+ Assert.Equal(string.Empty, routeNameMetadata.RouteName);
}
[Fact]
@@ -530,18 +341,18 @@ namespace Microsoft.AspNetCore.Mvc.Routing
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata();
- Assert.NotNull(routeValuesAddressMetadata);
- Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
- Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
+ var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata();
+ Assert.NotNull(routeNameMetadata);
+ Assert.Equal("namedRoute", routeNameMetadata.RouteName);
+ Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata();
- Assert.NotNull(routeValuesAddressMetadata);
- Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
- Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
+ var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata();
+ Assert.NotNull(routeNameMetadata);
+ Assert.Equal("namedRoute", routeNameMetadata.RouteName);
+ Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
});
}
@@ -569,25 +380,33 @@ namespace Microsoft.AspNetCore.Mvc.Routing
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
Assert.Equal(1, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
Assert.Equal(2, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("Details", matcherEndpoint.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("Products", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
Assert.Equal(1, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("Details", matcherEndpoint.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("Products", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
Assert.Equal(2, matcherEndpoint.Order);
});
}
@@ -631,10 +450,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Assert
Assert.Collection(
- endpoints.Cast().OrderBy(e => e.RoutePattern.RawText),
- e => Assert.Equal("en-CA/home/about", e.RoutePattern.RawText),
- e => Assert.Equal("en-NZ/home/index", e.RoutePattern.RawText),
- e => Assert.Equal("home/index", e.RoutePattern.RawText));
+ endpoints.Cast(),
+ e =>
+ {
+ Assert.Equal("{locale}/{controller}/{action}", e.RoutePattern.RawText);
+ Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("index", e.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("en-NZ", e.RoutePattern.RequiredValues["locale"]);
+ },
+ e =>
+ {
+ Assert.Equal("{locale}/{controller}/{action}", e.RoutePattern.RawText);
+ Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("about", e.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("en-CA", e.RoutePattern.RequiredValues["locale"]);
+ },
+ e =>
+ {
+ Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
+ Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("index", e.RoutePattern.RequiredValues["action"]);
+ });
}
[Fact]
@@ -689,8 +525,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
public void NoDefaultValues_RequiredValues_UsedToCreateDefaultValues()
{
// Arrange
- var expectedDefaults = new RouteValueDictionary(new { controller = "Foo", action = "Bar" });
- var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: expectedDefaults);
+ var requiredValues = new RouteValueDictionary(new { controller = "Foo", action = "Bar" });
+ var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
@@ -700,30 +536,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Assert
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType(endpoint);
- Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
- AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
- }
-
- [Fact]
- public void RequiredValues_NotPresent_InDefaultValues_IsAddedToDefaultValues()
- {
- // Arrange
- var requiredValues = new RouteValueDictionary(
- new { controller = "Foo", action = "Bar", subarea = "test" });
- var expectedDefaults = requiredValues;
- var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(
- CreateEndpointInfo(string.Empty, "{subarea}/{controller=Home}/{action=Index}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- var endpoint = Assert.Single(endpoints);
- var matcherEndpoint = Assert.IsType(endpoint);
- Assert.Equal("test/Foo/Bar", matcherEndpoint.RoutePattern.RawText);
- AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
+ Assert.Equal("{controller}/{action}", matcherEndpoint.RoutePattern.RawText);
+ AssertIsSubset(requiredValues, matcherEndpoint.RoutePattern.RequiredValues);
}
[Fact]
@@ -745,51 +559,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
Assert.Empty(endpoints);
}
- [Fact]
- public void RequiredValues_IsSubsetOf_DefaultValues()
- {
- // Arrange
- var requiredValues = new RouteValueDictionary(
- new { controller = "Foo", action = "Bar", subarea = "test" });
- var expectedDefaults = new RouteValueDictionary(
- new { controller = "Foo", action = "Bar", subarea = "test", subscription = "general" });
- var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(
- CreateEndpointInfo(
- string.Empty,
- "{controller=Home}/{action=Index}/{subscription=general}",
- defaults: new RouteValueDictionary(new { subarea = "test", })));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
[Fact]
public void RequiredValues_DoesNotMatchParameterDefaults_Included()
{
@@ -812,8 +581,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Assert
var endpoint = Assert.Single(endpoints);
var matcherEndpoint = Assert.IsType(endpoint);
- Assert.Equal("Foo/Baz/{id?}", matcherEndpoint.RoutePattern.RawText);
- AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
+ Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("Foo", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
+ Assert.Equal("Baz", matcherEndpoint.RoutePattern.RequiredValues["action"]);
+ Assert.Equal("Foo", matcherEndpoint.RoutePattern.Defaults["controller"]);
+ Assert.False(matcherEndpoint.RoutePattern.Defaults.ContainsKey("action"));
}
[Fact]
@@ -844,147 +616,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
}
- [Fact]
- public void Endpoints_ConventionalRoutes_NonDefaultAndDefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "Home", action = "Index" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller}/{action=Index}/{id?}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_ConventionalRoutes_DefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "Home", action = "Index" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller=Home}/{action=Index}/{id?}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("{controller=Home}/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(4, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_ConventionalRoutes_DefaultValues_Shortened()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller=TestController}/{action=TestAction}/{id=17}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(1, matcherEndpoint.Order);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(2, matcherEndpoint.Order);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(3, matcherEndpoint.Order);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(4, matcherEndpoint.Order);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction/{id=17}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(5, matcherEndpoint.Order);
- });
- }
-
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_EndpointInfoDefaultsNotModified()
{
@@ -1006,251 +637,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
Assert.Empty(endpointInfo.Defaults);
}
- [Fact]
- public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_Shortened()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(4, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(5, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_ConventionalRoutes_DefaultValuesAndOptional_Shortened()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller=TestController}/{action=TestAction}/{id=17}/{more?}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(4, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
- Assert.Equal(5, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_ConventionalRoutes_OptionalExtension_IncludeFullRouteAsHighPriority()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller}/{action=TestAction}.{ext?}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/{action=TestAction}.{ext?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction.{ext?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_ConventionalRoutes_MultipleOptionalAndCatchAll_IncludeFullRouteAsHighPriority()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
- dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
- name: string.Empty,
- template: "{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}"));
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(3, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(4, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
- [Fact]
- public void Endpoints_AttributeRoutes_CatchAllWithDefault_IncludeFullRouteAsHighPriority()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- "/TeamName/{*Name=DefaultName}/",
- new { controller = "TestController", action = "TestAction" });
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(0, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TeamName", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
- });
- }
-
[Fact]
public void Endpoints_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefaultCase()
{
@@ -1269,66 +655,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
(ep) =>
{
var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/{action=TESTACTION}/{id?}", matcherEndpoint.RoutePattern.RawText);
+ Assert.Equal("{controller}/{action=TESTACTION}/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
Assert.Equal(0, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, true);
- var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata();
- Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
-
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
- Assert.Equal(1, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
-
- var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata();
- Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
- },
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TESTACTION/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
- Assert.Equal(2, matcherEndpoint.Order);
- AssertMatchingSuppressed(matcherEndpoint, false);
-
- var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata();
- Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
- });
- }
-
- [Fact]
- public void Endpoints_AttributeRoutes_ActionMetadataDoesNotOverrideDataSourceMetadata()
- {
- // Arrange
- var actionDescriptorCollection = GetActionDescriptorCollection(
- CreateActionDescriptor(new { controller = "TestController", action = "TestAction" },
- "{controller}/{action}/{id?}",
- new List { new RouteValuesAddressMetadata("fakeroutename", new RouteValueDictionary(new { fake = "Fake!" })) })
- );
- var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
-
- // Act
- var endpoints = dataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) =>
- {
- var matcherEndpoint = Assert.IsType(ep);
- Assert.Equal("TestController/TestAction/{id?}", matcherEndpoint.RoutePattern.RawText);
- Assert.Equal(0, matcherEndpoint.Order);
-
- var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata();
- Assert.Equal("{controller}/{action}/{id?}", routeValuesAddress.RouteName);
- Assert.Equal("TestController", routeValuesAddress.RequiredValues["controller"]);
- Assert.Equal("TestAction", routeValuesAddress.RequiredValues["action"]);
+ Assert.Equal("TestAction", matcherEndpoint.RoutePattern.RequiredValues["action"]);
});
}
@@ -1345,16 +676,21 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var services = new ServiceCollection();
services.AddSingleton(actionDescriptorCollectionProvider);
+
+ var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
+ services.Configure(routeOptionsSetup.Configure);
services.AddRouting(options =>
{
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
});
+
var serviceProvider = services.BuildServiceProvider();
var dataSource = new MvcEndpointDataSource(
actionDescriptorCollectionProvider,
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())),
- serviceProvider.GetRequiredService());
+ serviceProvider.GetRequiredService(),
+ serviceProvider.GetRequiredService());
var defaultEndpointConventionBuilder = new DefaultEndpointConventionBuilder();
dataSource.AttributeRoutingConventionResolvers.Add((actionDescriptor) =>
@@ -1387,14 +723,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing
services.AddRouting();
services.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
- var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
- services.Configure(routeOptionsSetup.Configure);
- services.Configure(options =>
- {
- options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
- });
+ var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
+ services.Configure(routeOptionsSetup.Configure);
+ services.Configure(options =>
+ {
+ options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
+ });
- serviceProvider = services.BuildServiceProvider();
+ serviceProvider = services.BuildServiceProvider();
}
var parameterPolicyFactory = serviceProvider.GetRequiredService();
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs
index c0b84528e0..0117674a66 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs
@@ -25,11 +25,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"About/{id}",
defaults: new { page = "/About", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
+ requiredValues:new { page = "/About", });
var endpoint2 = CreateEndpoint(
"Admin/ManageUsers/{handler?}",
defaults: new { page = "/Admin/ManageUsers", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
+ requiredValues:new { page = "/Admin/ManageUsers", });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -54,11 +54,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"About/{id}",
defaults: new { page = "/About", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
+ requiredValues:new { page = "/About", });
var endpoint2 = CreateEndpoint(
"Admin/ManageUsers/{handler?}",
defaults: new { page = "/Admin/ManageUsers", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
+ requiredValues:new { page = "/Admin/ManageUsers", });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -82,11 +82,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"About/{id}",
defaults: new { page = "/About", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
+ requiredValues:new { page = "/About", });
var endpoint2 = CreateEndpoint(
"Admin/ManageUsers",
defaults: new { page = "/Admin/ManageUsers", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
+ requiredValues:new { page = "/Admin/ManageUsers", });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -112,11 +112,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"About/{id}",
defaults: new { page = "/About", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
+ requiredValues:new { page = "/About", });
var endpoint2 = CreateEndpoint(
"Admin/ManageUsers",
defaults: new { page = "/Admin/ManageUsers", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
+ requiredValues:new { page = "/Admin/ManageUsers", });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -142,11 +142,11 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = CreateEndpoint(
"About/{id}",
defaults: new { page = "/About", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
+ requiredValues:new { page = "/About", });
var endpoint2 = CreateEndpoint(
"Admin/ManageUsers",
defaults: new { page = "/Admin/ManageUsers", },
- metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
+ requiredValues:new { page = "/Admin/ManageUsers", });
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
@@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Routing
{
return new RouteEndpoint(
(httpContext) => Task.CompletedTask,
- RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
+ RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues),
order,
new EndpointMetadataCollection(metadata ?? Array.Empty()),
null);
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
index 4a11189cb2..1b833e2ce8 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
@@ -323,7 +323,7 @@ Hello from page";
var expected =
@"Link inside area
Link to external area
-Link to area action
+Link to area action
Link to non-area page ";
// Act
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs
index c2a0455553..50a237234d 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs
@@ -1294,8 +1294,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("/", result.Link);
}
-
-
[Fact]
public virtual async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea()
{
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html
index 72ba50bb99..d30a0a1a73 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html
@@ -1,4 +1,4 @@
-
+
@@ -12,8 +12,8 @@
ASP.NET vNext - About
| My Home
- | My About
- | My Help |
+ | My About
+ | My Help |
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html
index a3d85388ba..6d1ecf32b7 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html
@@ -1,4 +1,4 @@
-
+
@@ -12,8 +12,8 @@
ASP.NET vNext - Help
| My Home
- | My About
- | My Help |
+ |
My About
+ |
My Help |
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html
index d10ba0b6f4..9e85fde939 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html
@@ -1,4 +1,4 @@
-
+
@@ -19,8 +19,8 @@
ASP.NET vNext - Home Page
| My Home
- | My About
- | My Help |
+ |
My About
+ |
My Help |
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html
index c9bec6c144..3fbc0e8395 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html
@@ -1,4 +1,4 @@
-
+
@@ -12,8 +12,8 @@
ASP.NET vNext -
| My Home
- | My About
- | My Help |
+ |
My About
+ |
My Help |
Items:
diff --git a/src/Mvc/test/WebSites/RoutingWebSite/Controllers/DebugController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/DebugController.cs
new file mode 100644
index 0000000000..b217902a6c
--- /dev/null
+++ b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/DebugController.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Internal;
+
+namespace RoutingWebSite
+{
+ // This controller is reachable via traditional routing.
+ public class DebugController : Controller
+ {
+ private readonly DfaGraphWriter _graphWriter;
+ private readonly EndpointDataSource _endpointDataSource;
+
+ public DebugController(DfaGraphWriter graphWriter, EndpointDataSource endpointDataSource)
+ {
+ _graphWriter = graphWriter;
+ _endpointDataSource = endpointDataSource;
+ }
+
+ public IActionResult Graph()
+ {
+ var sw = new StringWriter();
+ _graphWriter.Write(_endpointDataSource, sw);
+
+ return Content(sw.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mvc/test/WebSites/RoutingWebSite/Startup.cs b/src/Mvc/test/WebSites/RoutingWebSite/Startup.cs
index 44bc08a25f..1f1be144e4 100644
--- a/src/Mvc/test/WebSites/RoutingWebSite/Startup.cs
+++ b/src/Mvc/test/WebSites/RoutingWebSite/Startup.cs
@@ -1,12 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.DependencyInjection;
namespace RoutingWebSite