Add endpoint disambiguation
- Better sample of metadata - Sample shows how conventional routing would work - Added endpoint disambiguation
This commit is contained in:
parent
57bf1494dd
commit
41f26dc69d
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class HttpMethodEndpointSelector : EndpointSelector
|
||||
{
|
||||
public override async Task SelectAsync(EndpointSelectorContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var snapshot = context.CreateSnapshot();
|
||||
|
||||
var fallback = new List<Endpoint>();
|
||||
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var endpoint = context.Endpoints[i];
|
||||
IHttpMethodMetadata metadata = null;
|
||||
|
||||
for (var j = endpoint.Metadata.Count - 1; j >= 0; j--)
|
||||
{
|
||||
metadata = endpoint.Metadata[j] as IHttpMethodMetadata;
|
||||
if (metadata != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
// No metadata.
|
||||
fallback.Add(endpoint);
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
else if (Matches(metadata, context.HttpContext.Request.Method))
|
||||
{
|
||||
// Do thing, this one matches
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a match.
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Now the list of endpoints only contains those that have an HTTP method preference AND match the current
|
||||
// request.
|
||||
await context.InvokeNextAsync();
|
||||
|
||||
if (context.Endpoints.Count == 0)
|
||||
{
|
||||
// Nothing matched, do the fallback.
|
||||
context.RestoreSnapshot(snapshot);
|
||||
context.Endpoints.Clear();
|
||||
|
||||
for (var i = 0; i < fallback.Count; i++)
|
||||
{
|
||||
context.Endpoints.Add(fallback[i]);
|
||||
}
|
||||
|
||||
await context.InvokeNextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Matches(IHttpMethodMetadata metadata, string httpMethod)
|
||||
{
|
||||
for (var i = 0; i < metadata.AllowedMethods.Count; i++)
|
||||
{
|
||||
if (string.Equals(metadata.AllowedMethods[i], httpMethod, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class HttpMethodMetadata : IHttpMethodMetadata
|
||||
{
|
||||
public HttpMethodMetadata(string httpMethod)
|
||||
{
|
||||
if (httpMethod == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpMethod));
|
||||
}
|
||||
|
||||
AllowedMethods = new[] { httpMethod, };
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> AllowedMethods { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public interface IAuthorizationPolicyMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public class AuthorizationPolicyMetadata : IAuthorizationPolicyMetadata
|
||||
{
|
||||
public AuthorizationPolicyMetadata(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public interface ICorsPolicyMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public class CorsPolicyMetadata : ICorsPolicyMetadata
|
||||
{
|
||||
public CorsPolicyMetadata(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public interface IHttpMethodMetadata
|
||||
{
|
||||
IReadOnlyList<string> AllowedMethods { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
|
|
@ -13,6 +15,7 @@ namespace DispatcherSample
|
|||
.UseIISIntegration()
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureLogging((c, b) => b.AddProvider(new ConsoleLoggerProvider((category, level) => true, includeScopes: false)))
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Dispatcher;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DispatcherSample
|
||||
|
|
@ -26,45 +28,24 @@ namespace DispatcherSample
|
|||
{
|
||||
services.Configure<DispatcherOptions>(options =>
|
||||
{
|
||||
options.Dispatchers.Add(CreateDispatcher(
|
||||
"{Endpoint=example}",
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "First" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the example!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example"),
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "Second" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example2")));
|
||||
options.Dispatchers.Add(new RouteTemplateDispatcher("{controller=Home}/{action=Index}/{id?}", ConstraintResolver)
|
||||
{
|
||||
Endpoints =
|
||||
{
|
||||
new SimpleEndpoint(Home_Index, Array.Empty<object>(), new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||
new SimpleEndpoint(Home_About, Array.Empty<object>(), new { controller = "Home", action = "About", }, "Home:About()"),
|
||||
new SimpleEndpoint(Admin_Index, Array.Empty<object>(), new { controller = "Admin", action = "Index", }, "Admin:Index()"),
|
||||
new SimpleEndpoint(Admin_GetUsers, new object[] { new HttpMethodMetadata("GET"), new AuthorizationPolicyMetadata("Admin"), }, new { controller = "Admin", action = "Users", }, "Admin:GetUsers()"),
|
||||
new SimpleEndpoint(Admin_EditUsers, new object[] { new HttpMethodMetadata("POST"), new AuthorizationPolicyMetadata("Admin"), }, new { controller = "Admin", action = "Users", }, "Admin:EditUsers()"),
|
||||
},
|
||||
Selectors =
|
||||
{
|
||||
new DispatcherValueEndpointSelector(),
|
||||
new HttpMethodEndpointSelector(),
|
||||
}
|
||||
}.InvokeAsync);
|
||||
|
||||
options.Dispatchers.Add(CreateDispatcher(
|
||||
"{Endpoint=example}/{Parameter=foo}",
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "First", Parameter = "param1" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the example for foo!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example"),
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "Second", Parameter = "param2" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example for foo!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example2")));
|
||||
|
||||
options.HandlerFactories.Add((endpoint) => (endpoint as RouteValuesEndpoint)?.HandlerFactory);
|
||||
options.HandlerFactories.Add((endpoint) => (endpoint as SimpleEndpoint)?.HandlerFactory);
|
||||
});
|
||||
|
||||
services.AddSingleton<UrlGenerator>();
|
||||
|
|
@ -72,35 +53,86 @@ namespace DispatcherSample
|
|||
services.AddDispatcher();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
|
||||
{
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
await context.Response.WriteAsync("<p>Middleware 1</p>");
|
||||
await next.Invoke();
|
||||
});
|
||||
|
||||
app.UseDispatcher();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
await context.Response.WriteAsync("<p>Middleware 2</p>");
|
||||
logger.LogInformation("Executing fake CORS middleware");
|
||||
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
var policy = feature.Endpoint?.Metadata.OfType<ICorsPolicyMetadata>().LastOrDefault();
|
||||
logger.LogInformation("using CORS policy {PolicyName}", policy?.Name ?? "default");
|
||||
|
||||
await next.Invoke();
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var urlGenerator = app.ApplicationServices.GetService<UrlGenerator>();
|
||||
var url = urlGenerator.GenerateURL(new RouteValueDictionary(new { Movie = "The Lion King", Character = "Mufasa" }), context);
|
||||
await context.Response.WriteAsync($"<p>Generated url: {url}</p>");
|
||||
logger.LogInformation("Executing fake AuthZ middleware");
|
||||
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
var policy = feature.Endpoint?.Metadata.OfType<IAuthorizationPolicyMetadata>().LastOrDefault();
|
||||
if (policy != null)
|
||||
{
|
||||
logger.LogInformation("using Auth policy {PolicyName}", policy.Name);
|
||||
}
|
||||
|
||||
await next.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
private static RequestDelegate CreateDispatcher(string routeTemplate, RouteValuesEndpoint endpoint, params RouteValuesEndpoint[] endpoints)
|
||||
public static Task Home_Index(HttpContext httpContext)
|
||||
{
|
||||
var dispatcher = new RouterDispatcher(new Route(new RouterEndpointSelector(new[] { endpoint }.Concat(endpoints)), routeTemplate, ConstraintResolver));
|
||||
return dispatcher.InvokeAsync;
|
||||
var urlGenerator = httpContext.RequestServices.GetService<UrlGenerator>();
|
||||
var url = urlGenerator.GenerateURL(new RouteValueDictionary(new { Movie = "The Lion King", Character = "Mufasa" }), httpContext);
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>Generated url: {url}</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Home_About(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>This is a dispatcher sample.</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_Index(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>This is the admin page.</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_GetUsers(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>Users: rynowak, jbagga</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_EditUsers(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>blerp</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,5 +10,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
Endpoint Endpoint { get; set; }
|
||||
|
||||
RequestDelegate RequestDelegate { get; set; }
|
||||
|
||||
DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
|
@ -8,6 +11,71 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public abstract class DispatcherBase
|
||||
{
|
||||
public abstract Task InvokeAsync(HttpContext httpContext);
|
||||
private IList<Endpoint> _endpoints;
|
||||
private IList<EndpointSelector> _endpointSelectors;
|
||||
|
||||
public virtual IList<Endpoint> Endpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
_endpoints = new List<Endpoint>();
|
||||
}
|
||||
|
||||
return _endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IList<EndpointSelector> Selectors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_endpointSelectors == null)
|
||||
{
|
||||
_endpointSelectors = new List<EndpointSelector>();
|
||||
}
|
||||
|
||||
return _endpointSelectors;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (await TryMatchAsync(httpContext))
|
||||
{
|
||||
if (feature.RequestDelegate != null)
|
||||
{
|
||||
// Short circuit, no need to select an endpoint.
|
||||
return;
|
||||
}
|
||||
|
||||
var selectorContext = new EndpointSelectorContext(httpContext, Endpoints.ToList(), Selectors);
|
||||
await selectorContext.InvokeNextAsync();
|
||||
|
||||
switch (selectorContext.Endpoints.Count)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
||||
feature.Endpoint = selectorContext.Endpoints[0];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Ambiguous bro!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<bool> TryMatchAsync(HttpContext httpContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,33 +7,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public class DispatcherFeature : IDispatcherFeature
|
||||
{
|
||||
private Endpoint _endpoint;
|
||||
private RequestDelegate _next;
|
||||
public Endpoint Endpoint { get; set; }
|
||||
|
||||
public Endpoint Endpoint
|
||||
{
|
||||
get
|
||||
{
|
||||
return _endpoint;
|
||||
}
|
||||
public RequestDelegate RequestDelegate { get; set; }
|
||||
|
||||
set
|
||||
{
|
||||
_endpoint = value;
|
||||
}
|
||||
}
|
||||
|
||||
public RequestDelegate RequestDelegate
|
||||
{
|
||||
get
|
||||
{
|
||||
return _next;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_next = value;
|
||||
}
|
||||
}
|
||||
public DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,28 +4,36 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DispatcherOptions _options;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public DispatcherMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
|
||||
public DispatcherMiddleware(IOptions<DispatcherOptions> options, ILogger<DispatcherMiddleware> logger, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +47,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
await entry(httpContext);
|
||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
||||
{
|
||||
_logger.LogInformation("Matched endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherValueEndpointSelector : EndpointSelector
|
||||
{
|
||||
public override Task SelectAsync(EndpointSelectorContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var dispatcherFeature = context.HttpContext.Features.Get<IDispatcherFeature>();
|
||||
|
||||
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var endpoint = context.Endpoints[i] as IDispatcherValueSelectableEndpoint;
|
||||
if (!CompareRouteValues(dispatcherFeature.Values, endpoint.Values))
|
||||
{
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return context.InvokeNextAsync();
|
||||
}
|
||||
|
||||
private bool CompareRouteValues(DispatcherValueCollection values, DispatcherValueCollection requiredValues)
|
||||
{
|
||||
foreach (var kvp in requiredValues)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kvp.Value.ToString()))
|
||||
{
|
||||
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,28 +4,36 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class EndpointMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DispatcherOptions _options;
|
||||
private RequestDelegate _next;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public EndpointMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
|
||||
public EndpointMiddleware(IOptions<DispatcherOptions> options, ILogger<DispatcherMiddleware> logger, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +55,17 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
if (feature.RequestDelegate != null)
|
||||
{
|
||||
await feature.RequestDelegate(context);
|
||||
_logger.LogInformation("Executing endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||
try
|
||||
{
|
||||
await feature.RequestDelegate(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("Executed endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class EndpointSelector
|
||||
{
|
||||
public abstract Task SelectAsync(EndpointSelectorContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public sealed class EndpointSelectorContext
|
||||
{
|
||||
private int _index;
|
||||
|
||||
public EndpointSelectorContext(HttpContext httpContext, IList<Endpoint> endpoints, IList<EndpointSelector> selectors)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (selectors == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectors));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
Endpoints = endpoints;
|
||||
Selectors = selectors;
|
||||
}
|
||||
|
||||
public IList<Endpoint> Endpoints { get; }
|
||||
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
public IList<EndpointSelector> Selectors { get; }
|
||||
|
||||
public Task InvokeNextAsync()
|
||||
{
|
||||
if (_index >= Selectors.Count)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var selector = Selectors[_index++];
|
||||
return selector.SelectAsync(this);
|
||||
}
|
||||
|
||||
public Snapshot CreateSnapshot()
|
||||
{
|
||||
return new Snapshot(_index, Endpoints);
|
||||
}
|
||||
|
||||
public void RestoreSnapshot(Snapshot snapshot)
|
||||
{
|
||||
snapshot.Apply(this);
|
||||
}
|
||||
|
||||
public struct Snapshot
|
||||
{
|
||||
private readonly int _index;
|
||||
private readonly Endpoint[] _endpoints;
|
||||
|
||||
internal Snapshot(int index, IList<Endpoint> endpoints)
|
||||
{
|
||||
_index = index;
|
||||
_endpoints = endpoints.ToArray();
|
||||
}
|
||||
|
||||
internal void Apply(EndpointSelectorContext context)
|
||||
{
|
||||
context._index = _index;
|
||||
|
||||
context.Endpoints.Clear();
|
||||
for (var i = 0; i < _endpoints.Length; i++)
|
||||
{
|
||||
context.Endpoints.Add(_endpoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IDispatcherValueSelectableEndpoint
|
||||
{
|
||||
DispatcherValueCollection Values { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,29 +8,34 @@ using Microsoft.AspNetCore.Http;
|
|||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class SimpleEndpoint : Endpoint
|
||||
public class SimpleEndpoint : Endpoint, IDispatcherValueSelectableEndpoint
|
||||
{
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate)
|
||||
: this(requestDelegate, Array.Empty<object>(), null)
|
||||
: this(requestDelegate, Array.Empty<object>(), null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory)
|
||||
: this(delegateFactory, Array.Empty<object>(), null)
|
||||
: this(delegateFactory, Array.Empty<object>(), null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata)
|
||||
: this(requestDelegate, metadata, null)
|
||||
: this(requestDelegate, metadata, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata)
|
||||
: this(delegateFactory, metadata, null)
|
||||
: this(delegateFactory, metadata, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata, string displayName)
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, object values)
|
||||
: this(delegateFactory, metadata, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata, object values, string displayName)
|
||||
{
|
||||
if (metadata == null)
|
||||
{
|
||||
|
|
@ -42,12 +47,13 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
throw new ArgumentNullException(nameof(requestDelegate));
|
||||
}
|
||||
|
||||
DisplayName = displayName;
|
||||
HandlerFactory = (next) => requestDelegate;
|
||||
Metadata = metadata.ToArray();
|
||||
DelegateFactory = (next) => requestDelegate;
|
||||
Values = new DispatcherValueCollection(values);
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, string displayName)
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, object values, string displayName)
|
||||
{
|
||||
if (metadata == null)
|
||||
{
|
||||
|
|
@ -59,15 +65,18 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
throw new ArgumentNullException(nameof(delegateFactory));
|
||||
}
|
||||
|
||||
DisplayName = displayName;
|
||||
HandlerFactory = delegateFactory;
|
||||
Metadata = metadata.ToArray();
|
||||
DelegateFactory = delegateFactory;
|
||||
Values = new DispatcherValueCollection(values);
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> DelegateFactory { get; }
|
||||
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; }
|
||||
|
||||
public DispatcherValueCollection Values { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
// 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 Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public static class DispatcherValueCollectionExtensions
|
||||
{
|
||||
public static RouteValueDictionary AsRouteValueDictionary(this DispatcherValueCollection values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
return values as RouteValueDictionary ?? new RouteValueDictionary(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class RouteTemplateDispatcher : DispatcherBase
|
||||
{
|
||||
private readonly IDictionary<string, IRouteConstraint> _constraints;
|
||||
private readonly RouteValueDictionary _defaults;
|
||||
private readonly TemplateMatcher _matcher;
|
||||
private readonly RouteTemplate _parsedTemplate;
|
||||
|
||||
public RouteTemplateDispatcher(
|
||||
string routeTemplate,
|
||||
IInlineConstraintResolver constraintResolver)
|
||||
: this(routeTemplate, constraintResolver, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteTemplateDispatcher(
|
||||
string routeTemplate,
|
||||
IInlineConstraintResolver constraintResolver,
|
||||
RouteValueDictionary defaults)
|
||||
: this(routeTemplate, constraintResolver, defaults, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteTemplateDispatcher(
|
||||
string routeTemplate,
|
||||
IInlineConstraintResolver constraintResolver,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints)
|
||||
{
|
||||
if (routeTemplate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeTemplate));
|
||||
}
|
||||
|
||||
if (constraintResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintResolver));
|
||||
}
|
||||
|
||||
RouteTemplate = routeTemplate;
|
||||
|
||||
try
|
||||
{
|
||||
// Data we parse from the template will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
_parsedTemplate = TemplateParser.Parse(routeTemplate);
|
||||
|
||||
_constraints = GetConstraints(constraintResolver, _parsedTemplate, constraints);
|
||||
_defaults = GetDefaults(_parsedTemplate, defaults);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(string.Empty, routeTemplate), exception);
|
||||
}
|
||||
|
||||
_matcher = new TemplateMatcher(_parsedTemplate, _defaults);
|
||||
}
|
||||
|
||||
public string RouteTemplate { get; }
|
||||
|
||||
protected override Task<bool> TryMatchAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
feature.Values = feature.Values ?? new RouteValueDictionary();
|
||||
|
||||
if (!_matcher.TryMatch(httpContext.Request.Path, (RouteValueDictionary)feature.Values))
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
foreach (var kvp in _constraints)
|
||||
{
|
||||
var constraint = kvp.Value;
|
||||
if (!constraint.Match(httpContext, null, kvp.Key, (RouteValueDictionary)feature.Values, RouteDirection.IncomingRequest))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static IDictionary<string, IRouteConstraint> GetConstraints(
|
||||
IInlineConstraintResolver inlineConstraintResolver,
|
||||
RouteTemplate parsedTemplate,
|
||||
IDictionary<string, object> constraints)
|
||||
{
|
||||
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
|
||||
|
||||
if (constraints != null)
|
||||
{
|
||||
foreach (var kvp in constraints)
|
||||
{
|
||||
constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.IsOptional)
|
||||
{
|
||||
constraintBuilder.SetOptional(parameter.Name);
|
||||
}
|
||||
|
||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
}
|
||||
|
||||
private static RouteValueDictionary GetDefaults(
|
||||
RouteTemplate parsedTemplate,
|
||||
RouteValueDictionary defaults)
|
||||
{
|
||||
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
if (result.ContainsKey(parameter.Name))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
|
||||
parameter.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class RouteValuesEndpoint : Endpoint
|
||||
{
|
||||
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate)
|
||||
: this(requiredValues, requestDelegate, Array.Empty<object>(), null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate, IEnumerable<object> metadata)
|
||||
: this(requiredValues, requestDelegate, metadata, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValuesEndpoint(
|
||||
RouteValueDictionary requiredValues,
|
||||
RequestDelegate requestDelegate,
|
||||
IEnumerable<object> metadata,
|
||||
string displayName)
|
||||
{
|
||||
if (requiredValues == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requiredValues));
|
||||
}
|
||||
|
||||
if (requestDelegate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestDelegate));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
RequiredValues = requiredValues;
|
||||
HandlerFactory = (next) => requestDelegate;
|
||||
Metadata = metadata.ToArray();
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; set; }
|
||||
|
||||
public RouteValueDictionary RequiredValues { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
/// </summary>
|
||||
public class RouterDispatcher : DispatcherBase
|
||||
{
|
||||
private readonly Endpoint _fallbackEndpoint;
|
||||
private readonly IRouter _router;
|
||||
|
||||
public RouterDispatcher(IRouter router)
|
||||
|
|
@ -23,17 +25,47 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
}
|
||||
|
||||
_router = router;
|
||||
_fallbackEndpoint = new UnknownEndpoint(_router);
|
||||
}
|
||||
|
||||
public async override Task InvokeAsync(HttpContext httpContext)
|
||||
protected override async Task<bool> TryMatchAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
|
||||
var routeContext = new RouteContext(httpContext);
|
||||
await _router.RouteAsync(routeContext);
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (routeContext.Handler == null)
|
||||
{
|
||||
// The route did not match, clear everything as it may have been set by the route.
|
||||
feature.Endpoint = null;
|
||||
feature.RequestDelegate = null;
|
||||
feature.Values = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
feature.Endpoint = feature.Endpoint ?? _fallbackEndpoint;
|
||||
feature.RequestDelegate = routeContext.Handler;
|
||||
feature.Values = routeContext.RouteData.Values;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class UnknownEndpoint : Endpoint
|
||||
{
|
||||
public UnknownEndpoint(IRouter router)
|
||||
{
|
||||
DisplayName = $"Endpoint for '{router}";
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata => Array.Empty<object>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class RouterEndpointSelector : IRouter, IRouteHandler
|
||||
{
|
||||
private readonly RouteValuesEndpoint[] _endpoints;
|
||||
|
||||
public RouterEndpointSelector(IEnumerable<RouteValuesEndpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
_endpoints = endpoints.ToArray();
|
||||
}
|
||||
|
||||
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (routeData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeData));
|
||||
}
|
||||
|
||||
var dispatcherFeature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (dispatcherFeature == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatDispatcherFeatureIsRequired(
|
||||
nameof(HttpContext),
|
||||
nameof(IDispatcherFeature),
|
||||
nameof(RouterEndpointSelector)));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _endpoints.Length; i++)
|
||||
{
|
||||
var endpoint = _endpoints[i];
|
||||
if (CompareRouteValues(routeData.Values, endpoint.RequiredValues))
|
||||
{
|
||||
dispatcherFeature.Endpoint = endpoint;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var handler = GetRequestHandler(context.HttpContext, context.RouteData);
|
||||
if (handler != null)
|
||||
{
|
||||
context.Handler = handler;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool CompareRouteValues(RouteValueDictionary values, RouteValueDictionary requiredValues)
|
||||
{
|
||||
foreach (var kvp in requiredValues)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kvp.Value.ToString()))
|
||||
{
|
||||
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue