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 :)
|
// This is a temporary layering issue, don't worry about :)
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||||
services.AddSingleton<IDefaultDispatcherFactory, TreeDispatcherFactory>();
|
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||||
|
|
||||||
// Imagine this was done by MVC or another framework.
|
// Imagine this was done by MVC or another framework.
|
||||||
services.AddSingleton<DispatcherDataSource>(ConfigureDispatcher());
|
services.AddSingleton<DispatcherDataSource>(ConfigureDispatcher());
|
||||||
services.AddSingleton<EndpointSelector, TemplateEndpointSelector>();
|
|
||||||
services.AddSingleton<EndpointSelector, HttpMethodEndpointSelector>();
|
services.AddSingleton<EndpointSelector, HttpMethodEndpointSelector>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -37,18 +36,20 @@ namespace DispatcherSample
|
||||||
{
|
{
|
||||||
Addresses =
|
Addresses =
|
||||||
{
|
{
|
||||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
|
new TemplateAddress("{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
|
new TemplateAddress("Home/About/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
|
||||||
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, "Admin:Index()"),
|
new TemplateAddress("Admin/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("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "Admin:GetUsers()/Admin:EditUsers()"),
|
||||||
},
|
},
|
||||||
Endpoints =
|
Endpoints =
|
||||||
{
|
{
|
||||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
new TemplateEndpoint("{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("Home/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, Admin_Index, "Admin:Index()"),
|
new TemplateEndpoint("Home/Index/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "GET", Admin_GetUsers, "Admin:GetUsers()", new AuthorizationPolicyMetadata("Admin")),
|
new TemplateEndpoint("Home/About/{id?}", new { controller = "Home", action = "About", }, Home_About, "Home:About()"),
|
||||||
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "POST", Admin_EditUsers, "Admin:EditUsers()", new AuthorizationPolicyMetadata("Admin")),
|
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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
Endpoint Endpoint { get; set; }
|
Endpoint Endpoint { get; set; }
|
||||||
|
|
||||||
RequestDelegate RequestDelegate { get; set; }
|
Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||||
|
|
||||||
DispatcherValueCollection Values { get; set; }
|
DispatcherValueCollection Values { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
|
|
||||||
_groups = new List<Address>[options.Value.Dispatchers.Count];
|
_groups = new List<Address>[options.Value.Matchers.Count];
|
||||||
for (var i = 0; i < options.Value.Dispatchers.Count; i++)
|
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>
|
internal class DefaultDispatcherConfigureOptions : IConfigureOptions<DispatcherOptions>
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<DispatcherDataSource> _dataSources;
|
private readonly IEnumerable<DispatcherDataSource> _dataSources;
|
||||||
private readonly IDefaultDispatcherFactory _dispatcherFactory;
|
private readonly IDefaultMatcherFactory _dispatcherFactory;
|
||||||
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
|
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
|
||||||
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories;
|
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories;
|
||||||
|
|
||||||
public DefaultDispatcherConfigureOptions(
|
public DefaultDispatcherConfigureOptions(
|
||||||
IDefaultDispatcherFactory dispatcherFactory,
|
IDefaultMatcherFactory dispatcherFactory,
|
||||||
IEnumerable<DispatcherDataSource> dataSources,
|
IEnumerable<DispatcherDataSource> dataSources,
|
||||||
IEnumerable<EndpointSelector> endpointSelectors,
|
IEnumerable<EndpointSelector> endpointSelectors,
|
||||||
IEnumerable<EndpointHandlerFactoryBase> handlerFactories)
|
IEnumerable<EndpointHandlerFactoryBase> handlerFactories)
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
throw new ArgumentNullException(nameof(options));
|
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)
|
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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public Endpoint Endpoint { get; set; }
|
public Endpoint Endpoint { get; set; }
|
||||||
|
|
||||||
public RequestDelegate RequestDelegate { get; set; }
|
public Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||||
|
|
||||||
public DispatcherValueCollection Values { get; set; }
|
public DispatcherValueCollection Values { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
_ambiguousEndpoints(logger, ambiguousEndpoints, null);
|
_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)
|
public static void NoEndpointsMatched(this ILogger logger, PathString pathString)
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,38 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
var feature = new DispatcherFeature();
|
var feature = new DispatcherFeature();
|
||||||
httpContext.Features.Set<IDispatcherFeature>(feature);
|
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);
|
await entry.Matcher.MatchAsync(context);
|
||||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public class DispatcherOptions
|
public class DispatcherOptions
|
||||||
{
|
{
|
||||||
public DispatcherCollection Dispatchers { get; } = new DispatcherCollection();
|
public MatcherCollection Matchers { get; } = new MatcherCollection();
|
||||||
|
|
||||||
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,28 +37,15 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
_next = next;
|
_next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var feature = context.Features.Get<IDispatcherFeature>();
|
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||||
if (feature.Endpoint != null && feature.RequestDelegate == null)
|
if (feature.Handler != 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)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Executing endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
_logger.LogInformation("Executing endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await feature.RequestDelegate(context);
|
await feature.Handler(_next)(httpContext);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +55,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _next(context);
|
await _next(httpContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,18 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
private int _index;
|
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)
|
if (httpContext == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(httpContext));
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(values));
|
||||||
|
}
|
||||||
|
|
||||||
if (endpoints == null)
|
if (endpoints == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(endpoints));
|
throw new ArgumentNullException(nameof(endpoints));
|
||||||
|
|
@ -31,6 +36,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpContext = httpContext;
|
HttpContext = httpContext;
|
||||||
|
Values = values;
|
||||||
Endpoints = endpoints;
|
Endpoints = endpoints;
|
||||||
Selectors = selectors;
|
Selectors = selectors;
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +47,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
public IList<EndpointSelector> Selectors { get; }
|
public IList<EndpointSelector> Selectors { get; }
|
||||||
|
|
||||||
|
public RequestDelegate ShortCircuit { get; set; }
|
||||||
|
|
||||||
|
public DispatcherValueCollection Values { get; }
|
||||||
|
|
||||||
public Task InvokeNextAsync()
|
public Task InvokeNextAsync()
|
||||||
{
|
{
|
||||||
if (_index >= Selectors.Count)
|
if (_index >= Selectors.Count)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
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
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public abstract class DispatcherBase : IAddressCollectionProvider, IEndpointCollectionProvider
|
public abstract class MatcherBase : IMatcher, IAddressCollectionProvider, IEndpointCollectionProvider
|
||||||
{
|
{
|
||||||
private List<Address> _addresses;
|
private List<Address> _addresses;
|
||||||
private List<Endpoint> _endpoints;
|
private List<Endpoint> _endpoints;
|
||||||
private List<EndpointSelector> _endpointSelectors;
|
private List<EndpointSelector> _endpointSelectors;
|
||||||
|
|
||||||
private object _initialize;
|
|
||||||
private bool _selectorsInitialized;
|
|
||||||
private readonly Func<object> _initializer;
|
|
||||||
private object _lock;
|
private object _lock;
|
||||||
|
|
||||||
private bool _servicesInitialized;
|
private bool _servicesInitialized;
|
||||||
|
private bool _selectorsInitialized;
|
||||||
|
private readonly Func<object> _selectorInitializer;
|
||||||
|
|
||||||
public DispatcherBase()
|
|
||||||
|
public MatcherBase()
|
||||||
{
|
{
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
_initializer = InitializeSelectors;
|
_selectorInitializer = InitializeSelectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger Logger { get; private set; }
|
||||||
|
|
@ -92,39 +91,37 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return ((IEndpointCollectionProvider)DataSource)?.Endpoints ?? _endpoints ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>();
|
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>();
|
context.Values = await MatchRequestAsync(context.HttpContext);
|
||||||
if (await TryMatchAsync(httpContext))
|
if (context.Values != null)
|
||||||
{
|
{
|
||||||
if (feature.RequestDelegate != null)
|
await SelectEndpointAsync(context, GetEndpoints());
|
||||||
{
|
|
||||||
// Short circuit, no need to select an endpoint.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.Endpoint = await SelectEndpointAsync(httpContext, GetEndpoints(), Selectors);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task<bool> TryMatchAsync(HttpContext httpContext)
|
protected virtual Task<DispatcherValueCollection> MatchRequestAsync(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
// By default don't apply any criteria.
|
// By default don't apply any criteria or provide any values.
|
||||||
return Task.FromResult(true);
|
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)
|
if (endpoints == null)
|
||||||
|
|
@ -132,25 +129,28 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
throw new ArgumentNullException(nameof(endpoints));
|
throw new ArgumentNullException(nameof(endpoints));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectors == null)
|
EnsureSelectorsInitialized();
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(selectors));
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyInitializer.EnsureInitialized(ref _initialize, ref _selectorsInitialized, ref _lock, _initializer);
|
var selectorContext = new EndpointSelectorContext(context.HttpContext, context.Values, endpoints.ToList(), Selectors.ToList());
|
||||||
|
|
||||||
var selectorContext = new EndpointSelectorContext(httpContext, endpoints.ToList(), selectors.ToList());
|
|
||||||
await selectorContext.InvokeNextAsync();
|
await selectorContext.InvokeNextAsync();
|
||||||
|
|
||||||
|
if (selectorContext.ShortCircuit != null)
|
||||||
|
{
|
||||||
|
context.ShortCircuit = selectorContext.ShortCircuit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (selectorContext.Endpoints.Count)
|
switch (selectorContext.Endpoints.Count)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
Logger.NoEndpointsMatched(httpContext.Request.Path);
|
Logger.NoEndpointsMatched(context.HttpContext.Request.Path);
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
Logger.EndpointMatched(selectorContext.Endpoints[0].DisplayName);
|
context.Endpoint = selectorContext.Endpoints[0];
|
||||||
return selectorContext.Endpoints[0];
|
|
||||||
|
Logger.EndpointMatched(context.Endpoint);
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var endpointNames = string.Join(
|
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()
|
private object InitializeSelectors()
|
||||||
{
|
{
|
||||||
foreach (var selector in Selectors)
|
foreach (var selector in Selectors)
|
||||||
|
|
@ -177,23 +183,25 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void EnsureServicesInitialized(HttpContext httpContext)
|
protected void EnsureServicesInitialized(MatcherContext context)
|
||||||
{
|
{
|
||||||
if (Volatile.Read(ref _servicesInitialized))
|
if (Volatile.Read(ref _servicesInitialized))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureServicesInitializedSlow(httpContext);
|
EnsureServicesInitializedSlow(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureServicesInitializedSlow(HttpContext httpContext)
|
private void EnsureServicesInitializedSlow(MatcherContext context)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!Volatile.Read(ref _servicesInitialized))
|
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.
|
// 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.
|
// 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
|
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; }
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Dispatcher;
|
using Microsoft.AspNetCore.Dispatcher;
|
||||||
|
|
@ -15,11 +14,10 @@ using Microsoft.AspNetCore.Routing.Logging;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||||
{
|
{
|
||||||
public class TreeDispatcher : DispatcherBase
|
public class TreeMatcher : MatcherBase
|
||||||
{
|
{
|
||||||
private bool _dataInitialized;
|
private bool _dataInitialized;
|
||||||
private object _lock;
|
private object _lock;
|
||||||
|
|
@ -27,31 +25,30 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||||
|
|
||||||
private readonly Func<Cache> _initializer;
|
private readonly Func<Cache> _initializer;
|
||||||
|
|
||||||
public TreeDispatcher()
|
public TreeMatcher()
|
||||||
{
|
{
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
_initializer = CreateCache;
|
_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 cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer);
|
||||||
|
|
||||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
var values = new RouteValueDictionary();
|
||||||
var values = feature.Values?.AsRouteValueDictionary() ?? new RouteValueDictionary();
|
context.Values = values;
|
||||||
feature.Values = values;
|
|
||||||
|
|
||||||
for (var i = 0; i < cache.Trees.Length; i++)
|
for (var i = 0; i < cache.Trees.Length; i++)
|
||||||
{
|
{
|
||||||
var tree = cache.Trees[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);
|
var treenumerator = new Treenumerator(tree.Root, tokenizer);
|
||||||
|
|
||||||
|
|
@ -64,23 +61,36 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||||
var matcher = item.TemplateMatcher;
|
var matcher = item.TemplateMatcher;
|
||||||
|
|
||||||
values.Clear();
|
values.Clear();
|
||||||
if (!matcher.TryMatch(httpContext.Request.Path, values))
|
if (!matcher.TryMatch(context.HttpContext.Request.Path, values))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText);
|
Logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText);
|
||||||
|
|
||||||
if (!MatchConstraints(httpContext, values, entry.Constraints))
|
if (!MatchConstraints(context.HttpContext, values, entry.Constraints))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
feature.Endpoint = await SelectEndpointAsync(httpContext, (Endpoint[])entry.Tag, Selectors);
|
await SelectEndpointAsync(context, (Endpoint[])entry.Tag);
|
||||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
if (context.ShortCircuit != null)
|
||||||
{
|
{
|
||||||
return;
|
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
|
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)
|
if (dataSource == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(dataSource));
|
throw new ArgumentNullException(nameof(dataSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
var dispatcher = new TreeDispatcher()
|
var matcher = new TreeMatcher()
|
||||||
{
|
{
|
||||||
DataSource = dataSource,
|
DataSource = dataSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var endpointSelector in endpointSelectors)
|
foreach (var endpointSelector in endpointSelectors)
|
||||||
{
|
{
|
||||||
dispatcher.Selectors.Add(endpointSelector);
|
matcher.Selectors.Add(endpointSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DispatcherEntry()
|
return new MatcherEntry()
|
||||||
{
|
{
|
||||||
AddressProvider = dispatcher,
|
AddressProvider = matcher,
|
||||||
Dispatcher = dispatcher.InvokeAsync,
|
Matcher = matcher,
|
||||||
EndpointProvider = dispatcher,
|
EndpointProvider = matcher,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
|
||||||
// This is a temporary layering issue, don't worry about it :)
|
// This is a temporary layering issue, don't worry about it :)
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||||
services.AddSingleton<IDefaultDispatcherFactory, TreeDispatcherFactory>();
|
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||||
|
|
||||||
services.Configure<DispatcherOptions>(ConfigureDispatcher);
|
services.Configure<DispatcherOptions>(ConfigureDispatcher);
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
|
||||||
|
|
||||||
public void ConfigureDispatcher(DispatcherOptions options)
|
public void ConfigureDispatcher(DispatcherOptions options)
|
||||||
{
|
{
|
||||||
options.Dispatchers.Add(new TreeDispatcher()
|
options.Matchers.Add(new TreeMatcher()
|
||||||
{
|
{
|
||||||
Endpoints =
|
Endpoints =
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,14 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.Request.Method = httpMethod;
|
httpContext.Request.Method = httpMethod;
|
||||||
|
|
||||||
var selector = new HttpMethodEndpointSelector();
|
var selector = new HttpMethodEndpointSelector();
|
||||||
var selectors = new List<EndpointSelector>()
|
var selectors = new List<EndpointSelector>()
|
||||||
{
|
{
|
||||||
selector
|
selector
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectorContext = new EndpointSelectorContext(httpContext, endpoints, selectors);
|
var selectorContext = new EndpointSelectorContext(httpContext, new DispatcherValueCollection(), endpoints, selectors);
|
||||||
return (selectorContext, selector);
|
return (selectorContext, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue