MVC reaction to RoutePattern required values
This commit is contained in:
parent
96cd055e05
commit
f157b78d93
|
|
@ -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<IActionInvokerProvider>())),
|
||||
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<ActionDescriptor> actionDescriptors)
|
||||
|
|
@ -137,5 +150,13 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertHasEndpoints(IReadOnlyList<Http.Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Expected endpoints from data source.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<DfaGraphWriter>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
|
||||
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<CompositeEndpointDataSource>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Endpoints:");
|
||||
|
|
@ -81,6 +108,7 @@ namespace MvcSandbox
|
|||
factory
|
||||
.AddConsole()
|
||||
.AddDebug();
|
||||
factory.SetMinimumLevel(LogLevel.Trace);
|
||||
})
|
||||
.UseIISIntegration()
|
||||
.UseKestrel()
|
||||
|
|
|
|||
|
|
@ -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<MvcEndpointInfo>();
|
||||
AttributeRoutingConventionResolvers = new List<Func<ActionDescriptor, DefaultEndpointConventionBuilder>>();
|
||||
|
|
@ -115,7 +101,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
lock (_lock)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
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<string, string> resolvedRequiredValues) ResolveDefaultsAndRequiredValues(ActionDescriptor action, RoutePattern attributeRoutePattern)
|
||||
{
|
||||
RouteValueDictionary updatedDefaults = null;
|
||||
IDictionary<string, string> 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<string, string>(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<Endpoint> endpoints,
|
||||
ref StringBuilder patternStringBuilder,
|
||||
ActionDescriptor action,
|
||||
int routeOrder,
|
||||
RoutePattern routePattern,
|
||||
IReadOnlyDictionary<string, object> allDefaults,
|
||||
IReadOnlyDictionary<string, object> nonInlineDefaults,
|
||||
string name,
|
||||
RouteValueDictionary dataTokens,
|
||||
IDictionary<string, IList<IParameterPolicy>> allParameterPolicies,
|
||||
bool suppressLinkGeneration,
|
||||
bool suppressPathMatching,
|
||||
List<Action<EndpointModel>> 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<RoutePatternPathSegment> segments)
|
||||
{
|
||||
if (sb == null)
|
||||
{
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
RoutePatternWriter.WriteString(sb, segments);
|
||||
var rawPattern = sb.ToString();
|
||||
sb.Length = 0;
|
||||
|
||||
return rawPattern;
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary<string, string> ResolveActionRouteValues(ActionDescriptor action, IReadOnlyDictionary<string, object> allDefaults)
|
||||
{
|
||||
Dictionary<string, string> 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<string, string>(action.RouteValues, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
resolvedRequiredValues[kvp.Key] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedRequiredValues ?? action.RouteValues;
|
||||
}
|
||||
|
||||
private void UpdatePathSegments(
|
||||
int i,
|
||||
ActionDescriptor action,
|
||||
IDictionary<string, string> resolvedRequiredValues,
|
||||
RoutePattern routePattern,
|
||||
List<RoutePatternPathSegment> newPathSegments,
|
||||
ref IDictionary<string, IList<IParameterPolicy>> allParameterPolicies)
|
||||
{
|
||||
List<RoutePatternPart> 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<string, string> resolvedRequiredValues,
|
||||
IReadOnlyDictionary<string, object> allDefaults,
|
||||
ref RouteValueDictionary nonInlineDefaults,
|
||||
List<RoutePatternPathSegment> 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<string, string> actionRouteValues,
|
||||
RoutePattern routePattern,
|
||||
string routeName,
|
||||
string patternRawText,
|
||||
IEnumerable<RoutePatternPathSegment> 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<object> 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<string, string> routeValues,
|
||||
RouteValueDictionary defaults,
|
||||
IEnumerable<RoutePatternPathSegment> segments)
|
||||
{
|
||||
foreach (var kvp in routeValues)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
defaults[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<object>()),
|
||||
null);
|
||||
|
|
|
|||
|
|
@ -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<object> metadata = null)
|
||||
{
|
||||
if (metadataCollection == null)
|
||||
metadata = metadata ?? new List<object>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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<object>()),
|
||||
null);
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ Hello from page";
|
|||
var expected =
|
||||
@"<a href=""/Accounts/Manage/RenderPartials"">Link inside area</a>
|
||||
<a href=""/Products/List/old/20"">Link to external area</a>
|
||||
<a href=""/Accounts"">Link to area action</a>
|
||||
<a href="""">Link to area action</a>
|
||||
<a href=""/Admin"">Link to non-area page</a>";
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1294,8 +1294,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("/", result.Link);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public virtual async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
<body>
|
||||
<h1 style="font-family: cursive;">ASP.NET vNext - About</h1>
|
||||
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
|
||||
| <a href="/Home/About" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/Home/Help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
<div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
<body>
|
||||
<h1 style="font-family: cursive;">ASP.NET vNext - Help</h1>
|
||||
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
|
||||
| <a href="/Home/About" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/Home/Help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
<div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
@ -19,8 +19,8 @@
|
|||
<body>
|
||||
<h1 style="font-family: cursive;">ASP.NET vNext - Home Page</h1>
|
||||
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
|
||||
| <a href="/Home/About" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/Home/Help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
<div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
<body>
|
||||
<h1 style="font-family: cursive;">ASP.NET vNext - </h1>
|
||||
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
|
||||
| <a href="/Home/About" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/Home/Help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
|
||||
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
|
||||
<div>
|
||||
|
||||
<div>Items: </div>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue