Changes due to davidfowl feedback
Changed dispatchers to IMatcher, added a context. Removed a bunch of outdated cruft and updated the sample to use attribute-routing like entries.
This commit is contained in:
parent
6b3d42f6bd
commit
63d2cc4637
|
|
@ -22,11 +22,10 @@ namespace DispatcherSample
|
|||
// This is a temporary layering issue, don't worry about :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||
services.AddSingleton<IDefaultDispatcherFactory, TreeDispatcherFactory>();
|
||||
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||
|
||||
// Imagine this was done by MVC or another framework.
|
||||
services.AddSingleton<DispatcherDataSource>(ConfigureDispatcher());
|
||||
services.AddSingleton<EndpointSelector, TemplateEndpointSelector>();
|
||||
services.AddSingleton<EndpointSelector, HttpMethodEndpointSelector>();
|
||||
|
||||
}
|
||||
|
|
@ -37,18 +36,20 @@ namespace DispatcherSample
|
|||
{
|
||||
Addresses =
|
||||
{
|
||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
|
||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, "Admin:Index()"),
|
||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "Admin:GetUsers()/Admin:EditUsers()"),
|
||||
new TemplateAddress("{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||
new TemplateAddress("Home/About/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
|
||||
new TemplateAddress("Admin/Index/{id?}", new { controller = "Admin", action = "Index", }, "Admin:Index()"),
|
||||
new TemplateAddress("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "Admin:GetUsers()/Admin:EditUsers()"),
|
||||
},
|
||||
Endpoints =
|
||||
{
|
||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "About", }, Home_About, "Home:About()"),
|
||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, Admin_Index, "Admin:Index()"),
|
||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "GET", Admin_GetUsers, "Admin:GetUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "POST", Admin_EditUsers, "Admin:EditUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
new TemplateEndpoint("{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new TemplateEndpoint("Home/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new TemplateEndpoint("Home/Index/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new TemplateEndpoint("Home/About/{id?}", new { controller = "Home", action = "About", }, Home_About, "Home:About()"),
|
||||
new TemplateEndpoint("Admin/Index/{id?}", new { controller = "Admin", action = "Index", }, Admin_Index, "Admin:Index()"),
|
||||
new TemplateEndpoint("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "GET", Admin_GetUsers, "Admin:GetUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
new TemplateEndpoint("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "POST", Admin_EditUsers, "Admin:EditUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
|
|
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
Endpoint Endpoint { get; set; }
|
||||
|
||||
RequestDelegate RequestDelegate { get; set; }
|
||||
Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||
|
||||
DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
_options = options.Value;
|
||||
|
||||
_groups = new List<Address>[options.Value.Dispatchers.Count];
|
||||
for (var i = 0; i < options.Value.Dispatchers.Count; i++)
|
||||
_groups = new List<Address>[options.Value.Matchers.Count];
|
||||
for (var i = 0; i < options.Value.Matchers.Count; i++)
|
||||
{
|
||||
_groups[i] = new List<Address>(options.Value.Dispatchers[i].AddressProvider?.Addresses ?? Array.Empty<Address>());
|
||||
_groups[i] = new List<Address>(options.Value.Matchers[i].AddressProvider?.Addresses ?? Array.Empty<Address>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
internal class DefaultDispatcherConfigureOptions : IConfigureOptions<DispatcherOptions>
|
||||
{
|
||||
private readonly IEnumerable<DispatcherDataSource> _dataSources;
|
||||
private readonly IDefaultDispatcherFactory _dispatcherFactory;
|
||||
private readonly IDefaultMatcherFactory _dispatcherFactory;
|
||||
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
|
||||
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories;
|
||||
|
||||
public DefaultDispatcherConfigureOptions(
|
||||
IDefaultDispatcherFactory dispatcherFactory,
|
||||
IDefaultMatcherFactory dispatcherFactory,
|
||||
IEnumerable<DispatcherDataSource> dataSources,
|
||||
IEnumerable<EndpointSelector> endpointSelectors,
|
||||
IEnumerable<EndpointHandlerFactoryBase> handlerFactories)
|
||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.Dispatchers.Add(_dispatcherFactory.CreateDispatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors));
|
||||
options.Matchers.Add(_dispatcherFactory.CreateDispatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors));
|
||||
|
||||
foreach (var handlerFactory in _handlerFactories)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,40 +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.ObjectModel;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherCollection : Collection<DispatcherEntry>
|
||||
{
|
||||
public void Add(DispatcherBase dispatcher)
|
||||
{
|
||||
if (dispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
Add(new DispatcherEntry()
|
||||
{
|
||||
Dispatcher = dispatcher.InvokeAsync,
|
||||
AddressProvider = dispatcher,
|
||||
EndpointProvider = dispatcher,
|
||||
});
|
||||
}
|
||||
|
||||
public void Add(RequestDelegate dispatcher)
|
||||
{
|
||||
if (dispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
Add(new DispatcherEntry()
|
||||
{
|
||||
Dispatcher = dispatcher,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
|
|
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public Endpoint Endpoint { get; set; }
|
||||
|
||||
public RequestDelegate RequestDelegate { get; set; }
|
||||
public Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||
|
||||
public DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
_ambiguousEndpoints(logger, ambiguousEndpoints, null);
|
||||
}
|
||||
|
||||
public static void EndpointMatched(this ILogger logger, string endpointName)
|
||||
public static void EndpointMatched(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_endpointMatched(logger, endpointName ?? "Unnamed endpoint", null);
|
||||
_endpointMatched(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void NoEndpointsMatched(this ILogger logger, PathString pathString)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_next = next;
|
||||
|
|
@ -42,12 +42,38 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
var feature = new DispatcherFeature();
|
||||
httpContext.Features.Set<IDispatcherFeature>(feature);
|
||||
|
||||
foreach (var entry in _options.Dispatchers)
|
||||
var context = new MatcherContext(httpContext);
|
||||
foreach (var entry in _options.Matchers)
|
||||
{
|
||||
await entry.Dispatcher(httpContext);
|
||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
||||
await entry.Matcher.MatchAsync(context);
|
||||
|
||||
if (context.ShortCircuit != null)
|
||||
{
|
||||
_logger.LogInformation("Matched endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||
feature.Endpoint = context.Endpoint;
|
||||
feature.Values = context.Values;
|
||||
|
||||
await context.ShortCircuit(httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Endpoint != null)
|
||||
{
|
||||
_logger.LogInformation("Matched endpoint {Endpoint}", context.Endpoint.DisplayName);
|
||||
|
||||
feature.Endpoint = context.Endpoint;
|
||||
feature.Values = context.Values;
|
||||
|
||||
// Associate this with the DispatcherEntry, not global
|
||||
for (var i = 0; i < _options.HandlerFactories.Count; i++)
|
||||
{
|
||||
var middleware = _options.HandlerFactories[i](feature.Endpoint);
|
||||
if (middleware != null)
|
||||
{
|
||||
feature.Handler = middleware;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public class DispatcherOptions
|
||||
{
|
||||
public DispatcherCollection Dispatchers { get; } = new DispatcherCollection();
|
||||
public MatcherCollection Matchers { get; } = new MatcherCollection();
|
||||
|
||||
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,28 +37,15 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
if (feature.Endpoint != null && feature.RequestDelegate == null)
|
||||
{
|
||||
for (var i = 0; i < _options.HandlerFactories.Count; i++)
|
||||
{
|
||||
var handler = _options.HandlerFactories[i](feature.Endpoint);
|
||||
if (handler != null)
|
||||
{
|
||||
feature.RequestDelegate = handler(_next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (feature.RequestDelegate != null)
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (feature.Handler != null)
|
||||
{
|
||||
_logger.LogInformation("Executing endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||
try
|
||||
{
|
||||
await feature.RequestDelegate(context);
|
||||
await feature.Handler(_next)(httpContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -68,7 +55,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
return;
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,18 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
private int _index;
|
||||
|
||||
public EndpointSelectorContext(HttpContext httpContext, IList<Endpoint> endpoints, IList<EndpointSelector> selectors)
|
||||
public EndpointSelectorContext(HttpContext httpContext, DispatcherValueCollection values, IList<Endpoint> endpoints, IList<EndpointSelector> selectors)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
|
|
@ -31,6 +36,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
Values = values;
|
||||
Endpoints = endpoints;
|
||||
Selectors = selectors;
|
||||
}
|
||||
|
|
@ -41,6 +47,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
public IList<EndpointSelector> Selectors { get; }
|
||||
|
||||
public RequestDelegate ShortCircuit { get; set; }
|
||||
|
||||
public DispatcherValueCollection Values { get; }
|
||||
|
||||
public Task InvokeNextAsync()
|
||||
{
|
||||
if (_index >= Selectors.Count)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IDefaultDispatcherFactory
|
||||
public interface IDefaultMatcherFactory
|
||||
{
|
||||
DispatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors);
|
||||
MatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for components that can select an <see cref="Endpoint"/> given the current request, as part
|
||||
/// of the execution of <see cref="DispatcherMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IMatcher"/> implementations can also optionally implement the <see cref="IEndpointCollectionProvider"/>
|
||||
/// and <see cref="IAddressCollectionProvider"/> interfaces to provide addition information.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use <see cref="DispatcherOptions"/> to register instances of <see cref="IMatcher"/> that will be used by the
|
||||
/// <see cref="DispatcherMiddleware"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="MatcherContext"/> associated with the current request.</param>
|
||||
/// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// An implementation should use data from the current request (<see cref="MatcherContext.HttpContext"/>) to select the
|
||||
/// <see cref="Endpoint"/> and set <see cref="MatcherContext.Endpoint"/> and optionally <see cref="MatcherContext.Values"/>
|
||||
/// to indicate a successful result.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the matcher encounters an immediate failure condition, the implementation should set
|
||||
/// <see cref="MatcherContext.ShortCircuit"/> to a <see cref="RequestDelegate"/> that will respond to the current request.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Task MatchAsync(MatcherContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,23 +14,22 @@ using Microsoft.Extensions.Primitives;
|
|||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class DispatcherBase : IAddressCollectionProvider, IEndpointCollectionProvider
|
||||
public abstract class MatcherBase : IMatcher, IAddressCollectionProvider, IEndpointCollectionProvider
|
||||
{
|
||||
private List<Address> _addresses;
|
||||
private List<Endpoint> _endpoints;
|
||||
private List<EndpointSelector> _endpointSelectors;
|
||||
|
||||
private object _initialize;
|
||||
private bool _selectorsInitialized;
|
||||
private readonly Func<object> _initializer;
|
||||
private object _lock;
|
||||
|
||||
private bool _servicesInitialized;
|
||||
private bool _selectorsInitialized;
|
||||
private readonly Func<object> _selectorInitializer;
|
||||
|
||||
public DispatcherBase()
|
||||
|
||||
public MatcherBase()
|
||||
{
|
||||
_lock = new object();
|
||||
_initializer = InitializeSelectors;
|
||||
_selectorInitializer = InitializeSelectors;
|
||||
}
|
||||
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
|
@ -92,39 +91,37 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
return ((IEndpointCollectionProvider)DataSource)?.Endpoints ?? _endpoints ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>();
|
||||
}
|
||||
|
||||
public virtual async Task InvokeAsync(HttpContext httpContext)
|
||||
public virtual async Task MatchAsync(MatcherContext context)
|
||||
{
|
||||
if (httpContext == null)
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureServicesInitialized(httpContext);
|
||||
EnsureServicesInitialized(context);
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (await TryMatchAsync(httpContext))
|
||||
context.Values = await MatchRequestAsync(context.HttpContext);
|
||||
if (context.Values != null)
|
||||
{
|
||||
if (feature.RequestDelegate != null)
|
||||
{
|
||||
// Short circuit, no need to select an endpoint.
|
||||
return;
|
||||
}
|
||||
|
||||
feature.Endpoint = await SelectEndpointAsync(httpContext, GetEndpoints(), Selectors);
|
||||
await SelectEndpointAsync(context, GetEndpoints());
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task<bool> TryMatchAsync(HttpContext httpContext)
|
||||
protected virtual Task<DispatcherValueCollection> MatchRequestAsync(HttpContext httpContext)
|
||||
{
|
||||
// By default don't apply any criteria.
|
||||
return Task.FromResult(true);
|
||||
// By default don't apply any criteria or provide any values.
|
||||
return Task.FromResult(new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
protected virtual async Task<Endpoint> SelectEndpointAsync(HttpContext httpContext, IEnumerable<Endpoint> endpoints, IEnumerable<EndpointSelector> selectors)
|
||||
protected virtual void InitializeServices(IServiceProvider services)
|
||||
{
|
||||
if (httpContext == null)
|
||||
}
|
||||
|
||||
protected virtual async Task SelectEndpointAsync(MatcherContext context, IEnumerable<Endpoint> endpoints)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (endpoints == null)
|
||||
|
|
@ -132,25 +129,28 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (selectors == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectors));
|
||||
}
|
||||
EnsureSelectorsInitialized();
|
||||
|
||||
LazyInitializer.EnsureInitialized(ref _initialize, ref _selectorsInitialized, ref _lock, _initializer);
|
||||
|
||||
var selectorContext = new EndpointSelectorContext(httpContext, endpoints.ToList(), selectors.ToList());
|
||||
var selectorContext = new EndpointSelectorContext(context.HttpContext, context.Values, endpoints.ToList(), Selectors.ToList());
|
||||
await selectorContext.InvokeNextAsync();
|
||||
|
||||
if (selectorContext.ShortCircuit != null)
|
||||
{
|
||||
context.ShortCircuit = selectorContext.ShortCircuit;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (selectorContext.Endpoints.Count)
|
||||
{
|
||||
case 0:
|
||||
Logger.NoEndpointsMatched(httpContext.Request.Path);
|
||||
return null;
|
||||
Logger.NoEndpointsMatched(context.HttpContext.Request.Path);
|
||||
return;
|
||||
|
||||
case 1:
|
||||
Logger.EndpointMatched(selectorContext.Endpoints[0].DisplayName);
|
||||
return selectorContext.Endpoints[0];
|
||||
context.Endpoint = selectorContext.Endpoints[0];
|
||||
|
||||
Logger.EndpointMatched(context.Endpoint);
|
||||
return;
|
||||
|
||||
default:
|
||||
var endpointNames = string.Join(
|
||||
|
|
@ -167,6 +167,12 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
}
|
||||
}
|
||||
|
||||
protected void EnsureSelectorsInitialized()
|
||||
{
|
||||
object _ = null;
|
||||
LazyInitializer.EnsureInitialized(ref _, ref _selectorsInitialized, ref _lock, _selectorInitializer);
|
||||
}
|
||||
|
||||
private object InitializeSelectors()
|
||||
{
|
||||
foreach (var selector in Selectors)
|
||||
|
|
@ -177,23 +183,25 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
return null;
|
||||
}
|
||||
|
||||
protected void EnsureServicesInitialized(HttpContext httpContext)
|
||||
protected void EnsureServicesInitialized(MatcherContext context)
|
||||
{
|
||||
if (Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureServicesInitializedSlow(httpContext);
|
||||
EnsureServicesInitializedSlow(context);
|
||||
}
|
||||
|
||||
private void EnsureServicesInitializedSlow(HttpContext httpContext)
|
||||
private void EnsureServicesInitializedSlow(MatcherContext context)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
Logger = httpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
|
||||
var services = context.HttpContext.RequestServices;
|
||||
Logger = services.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
|
||||
InitializeServices(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.ObjectModel;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class MatcherCollection : Collection<MatcherEntry>
|
||||
{
|
||||
public void Add(MatcherBase matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(matcher));
|
||||
}
|
||||
|
||||
Add(new MatcherEntry()
|
||||
{
|
||||
Matcher = matcher,
|
||||
AddressProvider = matcher,
|
||||
EndpointProvider = matcher,
|
||||
});
|
||||
}
|
||||
|
||||
public void Add(IMatcher matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(matcher));
|
||||
}
|
||||
|
||||
Add(new MatcherEntry()
|
||||
{
|
||||
Matcher = matcher,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IMatcher.MatchAsync(MatcherContext)"/>.
|
||||
/// </summary>
|
||||
public class MatcherContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MatcherContext"/> for the current request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
public MatcherContext(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Http.HttpContext"/> associated with the current request.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Endpoint"/> selected by the matcher.
|
||||
/// </summary>
|
||||
public Endpoint Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short-circuit delegate provided by the matcher.
|
||||
/// </summary>
|
||||
public RequestDelegate ShortCircuit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a <see cref="DispatcherValueCollection"/> provided by the matcher.
|
||||
/// </summary>
|
||||
public DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherEntry
|
||||
public class MatcherEntry
|
||||
{
|
||||
public RequestDelegate Dispatcher { get; set; }
|
||||
public IMatcher Matcher { get; set; }
|
||||
|
||||
public IAddressCollectionProvider AddressProvider { get; set; }
|
||||
|
||||
|
|
@ -1,55 +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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class TemplateEndpointSelector : 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 ITemplateEndpoint;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,157 +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.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,72 +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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter to plug an <see cref="IRouter"/> into a dispatcher.
|
||||
/// </summary>
|
||||
public class RouterDispatcher : DispatcherBase
|
||||
{
|
||||
private readonly Endpoint _fallbackEndpoint;
|
||||
private readonly IRouter _router;
|
||||
|
||||
public RouterDispatcher(IRouter router)
|
||||
{
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
_router = router;
|
||||
_fallbackEndpoint = new UnknownEndpoint(_router);
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
|
|
@ -15,11 +14,10 @@ using Microsoft.AspNetCore.Routing.Logging;
|
|||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class TreeDispatcher : DispatcherBase
|
||||
public class TreeMatcher : MatcherBase
|
||||
{
|
||||
private bool _dataInitialized;
|
||||
private object _lock;
|
||||
|
|
@ -27,31 +25,30 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
|
||||
private readonly Func<Cache> _initializer;
|
||||
|
||||
public TreeDispatcher()
|
||||
public TreeMatcher()
|
||||
{
|
||||
_lock = new object();
|
||||
_initializer = CreateCache;
|
||||
}
|
||||
|
||||
public override async Task InvokeAsync(HttpContext httpContext)
|
||||
public override async Task MatchAsync(MatcherContext context)
|
||||
{
|
||||
if (httpContext == null)
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureServicesInitialized(httpContext);
|
||||
EnsureServicesInitialized(context);
|
||||
|
||||
var cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer);
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
var values = feature.Values?.AsRouteValueDictionary() ?? new RouteValueDictionary();
|
||||
feature.Values = values;
|
||||
var values = new RouteValueDictionary();
|
||||
context.Values = values;
|
||||
|
||||
for (var i = 0; i < cache.Trees.Length; i++)
|
||||
{
|
||||
var tree = cache.Trees[i];
|
||||
var tokenizer = new PathTokenizer(httpContext.Request.Path);
|
||||
var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
|
||||
|
||||
var treenumerator = new Treenumerator(tree.Root, tokenizer);
|
||||
|
||||
|
|
@ -64,23 +61,36 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
var matcher = item.TemplateMatcher;
|
||||
|
||||
values.Clear();
|
||||
if (!matcher.TryMatch(httpContext.Request.Path, values))
|
||||
if (!matcher.TryMatch(context.HttpContext.Request.Path, values))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText);
|
||||
|
||||
if (!MatchConstraints(httpContext, values, entry.Constraints))
|
||||
if (!MatchConstraints(context.HttpContext, values, entry.Constraints))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
feature.Endpoint = await SelectEndpointAsync(httpContext, (Endpoint[])entry.Tag, Selectors);
|
||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
||||
await SelectEndpointAsync(context, (Endpoint[])entry.Tag);
|
||||
if (context.ShortCircuit != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Endpoint != null)
|
||||
{
|
||||
if (context.Endpoint is ITemplateEndpoint templateEndpoint)
|
||||
{
|
||||
foreach (var kvp in templateEndpoint.Values)
|
||||
{
|
||||
context.Values[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,30 +7,30 @@ using Microsoft.AspNetCore.Dispatcher;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class TreeDispatcherFactory : IDefaultDispatcherFactory
|
||||
public class TreeMatcherFactory : IDefaultMatcherFactory
|
||||
{
|
||||
public DispatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors)
|
||||
public MatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors)
|
||||
{
|
||||
if (dataSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSource));
|
||||
}
|
||||
|
||||
var dispatcher = new TreeDispatcher()
|
||||
var matcher = new TreeMatcher()
|
||||
{
|
||||
DataSource = dataSource,
|
||||
};
|
||||
|
||||
foreach (var endpointSelector in endpointSelectors)
|
||||
{
|
||||
dispatcher.Selectors.Add(endpointSelector);
|
||||
matcher.Selectors.Add(endpointSelector);
|
||||
}
|
||||
|
||||
return new DispatcherEntry()
|
||||
return new MatcherEntry()
|
||||
{
|
||||
AddressProvider = dispatcher,
|
||||
Dispatcher = dispatcher.InvokeAsync,
|
||||
EndpointProvider = dispatcher,
|
||||
AddressProvider = matcher,
|
||||
Matcher = matcher,
|
||||
EndpointProvider = matcher,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
|
|||
// This is a temporary layering issue, don't worry about it :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||
services.AddSingleton<IDefaultDispatcherFactory, TreeDispatcherFactory>();
|
||||
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||
|
||||
services.Configure<DispatcherOptions>(ConfigureDispatcher);
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
|
|||
|
||||
public void ConfigureDispatcher(DispatcherOptions options)
|
||||
{
|
||||
options.Dispatchers.Add(new TreeDispatcher()
|
||||
options.Matchers.Add(new TreeMatcher()
|
||||
{
|
||||
Endpoints =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,13 +84,14 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Method = httpMethod;
|
||||
|
||||
var selector = new HttpMethodEndpointSelector();
|
||||
var selectors = new List<EndpointSelector>()
|
||||
{
|
||||
selector
|
||||
};
|
||||
|
||||
var selectorContext = new EndpointSelectorContext(httpContext, endpoints, selectors);
|
||||
var selectorContext = new EndpointSelectorContext(httpContext, new DispatcherValueCollection(), endpoints, selectors);
|
||||
return (selectorContext, selector);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue