Add a dispatcher by default (#462)
This change adds the TreeDispatcher by default to the pipeline. You can register DispatcherDataSource instances to configure it.
This commit is contained in:
parent
9c30dd1256
commit
abb41302e9
|
|
@ -22,27 +22,16 @@ namespace DispatcherSample
|
|||
var fallback = new List<Endpoint>();
|
||||
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var endpoint = context.Endpoints[i];
|
||||
ITemplateEndpoint metadata = null;
|
||||
|
||||
for (var j = endpoint.Metadata.Count - 1; j >= 0; j--)
|
||||
{
|
||||
metadata = endpoint.Metadata[j] as ITemplateEndpoint;
|
||||
if (metadata != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
var endpoint = context.Endpoints[i] as ITemplateEndpoint;
|
||||
if (endpoint == null || endpoint.HttpMethod == null)
|
||||
{
|
||||
// No metadata.
|
||||
fallback.Add(endpoint);
|
||||
fallback.Add(context.Endpoints[i]);
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
else if (Matches(metadata, context.HttpContext.Request.Method))
|
||||
else if (string.Equals(endpoint.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Do thing, this one matches
|
||||
// This one matches.
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -69,10 +58,5 @@ namespace DispatcherSample
|
|||
await context.InvokeNextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Matches(ITemplateEndpoint endpoint, string httpMethod)
|
||||
{
|
||||
return string.Equals(endpoint.HttpMethod, httpMethod, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,46 +17,42 @@ namespace DispatcherSample
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly static IInlineConstraintResolver ConstraintResolver = new DefaultInlineConstraintResolver(
|
||||
new OptionsManager<RouteOptions>(
|
||||
new OptionsFactory<RouteOptions>(
|
||||
Enumerable.Empty<IConfigureOptions<RouteOptions>>(),
|
||||
Enumerable.Empty<IPostConfigureOptions<RouteOptions>>())));
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.Configure<DispatcherOptions>(options =>
|
||||
{
|
||||
options.Dispatchers.Add(new TreeDispatcher()
|
||||
{
|
||||
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()"),
|
||||
},
|
||||
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")),
|
||||
},
|
||||
Selectors =
|
||||
{
|
||||
new TemplateEndpointSelector(),
|
||||
new HttpMethodEndpointSelector(),
|
||||
}
|
||||
});
|
||||
|
||||
options.HandlerFactories.Add((endpoint) => (endpoint as TemplateEndpoint)?.HandlerFactory);
|
||||
});
|
||||
|
||||
services.AddDispatcher();
|
||||
|
||||
// This is a temporary layering issue, don't worry about :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||
services.AddSingleton<IDefaultDispatcherFactory, TreeDispatcherFactory>();
|
||||
|
||||
// Imagine this was done by MVC or another framework.
|
||||
services.AddSingleton<DispatcherDataSource>(ConfigureDispatcher());
|
||||
services.AddSingleton<EndpointSelector, TemplateEndpointSelector>();
|
||||
services.AddSingleton<EndpointSelector, HttpMethodEndpointSelector>();
|
||||
|
||||
}
|
||||
|
||||
public DefaultDispatcherDataSource ConfigureDispatcher()
|
||||
{
|
||||
return new DefaultDispatcherDataSource()
|
||||
{
|
||||
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()"),
|
||||
},
|
||||
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")),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// 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.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class CompositeDispatcherDataSource : DispatcherDataSource
|
||||
{
|
||||
private readonly DispatcherDataSource[] _dataSources;
|
||||
|
||||
public CompositeDispatcherDataSource(IEnumerable<DispatcherDataSource> dataSources)
|
||||
{
|
||||
if (dataSources == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSources));
|
||||
}
|
||||
|
||||
_dataSources = dataSources.ToArray();
|
||||
|
||||
var changeTokens = new IChangeToken[_dataSources.Length];
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
changeTokens[i] = _dataSources[i].ChangeToken;
|
||||
}
|
||||
|
||||
ChangeToken = new CompositeChangeToken(changeTokens);
|
||||
}
|
||||
|
||||
public override IChangeToken ChangeToken { get; }
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddesses()
|
||||
{
|
||||
var addresses = new List<Address>();
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
addresses.AddRange(((IAddressCollectionProvider)_dataSources[i]).Addresses);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<Endpoint> GetEndpoints()
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
endpoints.AddRange(((IEndpointCollectionProvider)_dataSources[i]).Endpoints);
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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 Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
internal class DefaultDispatcherConfigureOptions : IConfigureOptions<DispatcherOptions>
|
||||
{
|
||||
private readonly IEnumerable<DispatcherDataSource> _dataSources;
|
||||
private readonly IDefaultDispatcherFactory _dispatcherFactory;
|
||||
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
|
||||
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories;
|
||||
|
||||
public DefaultDispatcherConfigureOptions(
|
||||
IDefaultDispatcherFactory dispatcherFactory,
|
||||
IEnumerable<DispatcherDataSource> dataSources,
|
||||
IEnumerable<EndpointSelector> endpointSelectors,
|
||||
IEnumerable<EndpointHandlerFactoryBase> handlerFactories)
|
||||
{
|
||||
_dispatcherFactory = dispatcherFactory;
|
||||
_dataSources = dataSources;
|
||||
_endpointSelectors = endpointSelectors;
|
||||
_handlerFactories = handlerFactories;
|
||||
}
|
||||
|
||||
public void Configure(DispatcherOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.Dispatchers.Add(_dispatcherFactory.CreateDispatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors));
|
||||
|
||||
foreach (var handlerFactory in _handlerFactories)
|
||||
{
|
||||
options.HandlerFactories.Add(handlerFactory.CreateHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
public IList<Address> Addresses => _addresses;
|
||||
|
||||
public IList<Endpoint> Endpoint { get; } = new List<Endpoint>();
|
||||
public IList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddesses() => _addresses;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Dispatcher.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
|
@ -16,10 +18,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddSingleton<IStartupFilter, DispatcherEndpointStartupFilter>();
|
||||
// Adds the EndpointMiddleare at the end of the pipeline if the DispatcherMiddleware is in use.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, DispatcherEndpointStartupFilter>());
|
||||
|
||||
// Adds a default dispatcher which will collect all data sources and endpoint selectors from DI.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<DispatcherOptions>, DefaultDispatcherConfigureOptions>());
|
||||
|
||||
services.AddSingleton<AddressTable, DefaultAddressTable>();
|
||||
services.AddSingleton<TemplateAddressSelector>();
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointHandlerFactoryBase, TemplateEndpointHandlerFactory>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// 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>
|
||||
/// <para>
|
||||
/// Base class for implementations that can create a middleware-like delegate from an <see cref="Endpoint"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementations registered in the application services using the service type <see cref="EndpointHandlerFactoryBase"/>
|
||||
/// will be automatically added to <see cref="DispatcherOptions.HandlerFactories"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class EndpointHandlerFactoryBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a middleware-like delegate for the provided <see cref="Endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The <see cref="Endpoint"/> that will execute for the current request.</param>
|
||||
/// <returns>An <see cref="Func{RequestDelegate, RequestDelegate}"/> or <c>null</c>.</returns>
|
||||
public abstract Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IDefaultDispatcherFactory
|
||||
{
|
||||
DispatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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
|
||||
{
|
||||
public class TemplateEndpointHandlerFactory : EndpointHandlerFactoryBase
|
||||
{
|
||||
public override Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoint));
|
||||
}
|
||||
|
||||
if (endpoint is TemplateEndpoint templateEndpoint)
|
||||
{
|
||||
return templateEndpoint.HandlerFactory;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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 Microsoft.AspNetCore.Dispatcher;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class TreeDispatcherFactory : IDefaultDispatcherFactory
|
||||
{
|
||||
public DispatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors)
|
||||
{
|
||||
if (dataSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSource));
|
||||
}
|
||||
|
||||
var dispatcher = new TreeDispatcher()
|
||||
{
|
||||
DataSource = dataSource,
|
||||
};
|
||||
|
||||
foreach (var endpointSelector in endpointSelectors)
|
||||
{
|
||||
dispatcher.Selectors.Add(endpointSelector);
|
||||
}
|
||||
|
||||
return new DispatcherEntry()
|
||||
{
|
||||
AddressProvider = dispatcher,
|
||||
Dispatcher = dispatcher.InvokeAsync,
|
||||
EndpointProvider = dispatcher,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue