diff --git a/samples/DispatcherSample/HttpMethodEndpointSelector.cs b/samples/DispatcherSample/HttpMethodEndpointSelector.cs index 594193885f..3885eb3486 100644 --- a/samples/DispatcherSample/HttpMethodEndpointSelector.cs +++ b/samples/DispatcherSample/HttpMethodEndpointSelector.cs @@ -22,27 +22,16 @@ namespace DispatcherSample var fallback = new List(); 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); - } } } diff --git a/samples/DispatcherSample/Startup.cs b/samples/DispatcherSample/Startup.cs index 01681344c3..68f8467eaf 100644 --- a/samples/DispatcherSample/Startup.cs +++ b/samples/DispatcherSample/Startup.cs @@ -17,46 +17,42 @@ namespace DispatcherSample { public class Startup { - private readonly static IInlineConstraintResolver ConstraintResolver = new DefaultInlineConstraintResolver( - new OptionsManager( - new OptionsFactory( - Enumerable.Empty>(), - Enumerable.Empty>()))); - public void ConfigureServices(IServiceCollection services) { - services.Configure(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(); + services.AddSingleton(); + + // Imagine this was done by MVC or another framework. + services.AddSingleton(ConfigureDispatcher()); + services.AddSingleton(); + services.AddSingleton(); + + } + + 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 logger) diff --git a/src/Microsoft.AspNetCore.Dispatcher/CompositeDispatcherDataSource.cs b/src/Microsoft.AspNetCore.Dispatcher/CompositeDispatcherDataSource.cs new file mode 100644 index 0000000000..336b01558c --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/CompositeDispatcherDataSource.cs @@ -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 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
GetAddesses() + { + var addresses = new List
(); + for (var i = 0; i < _dataSources.Length; i++) + { + addresses.AddRange(((IAddressCollectionProvider)_dataSources[i]).Addresses); + } + + return addresses; + } + + protected override IReadOnlyList GetEndpoints() + { + var endpoints = new List(); + for (var i = 0; i < _dataSources.Length; i++) + { + endpoints.AddRange(((IEndpointCollectionProvider)_dataSources[i]).Endpoints); + } + + return endpoints; + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherConfigureOptions.cs b/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherConfigureOptions.cs new file mode 100644 index 0000000000..16351dc924 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherConfigureOptions.cs @@ -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 + { + private readonly IEnumerable _dataSources; + private readonly IDefaultDispatcherFactory _dispatcherFactory; + private readonly IEnumerable _endpointSelectors; + private readonly IEnumerable _handlerFactories; + + public DefaultDispatcherConfigureOptions( + IDefaultDispatcherFactory dispatcherFactory, + IEnumerable dataSources, + IEnumerable endpointSelectors, + IEnumerable 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); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherDataSource.cs b/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherDataSource.cs index 026f3b2167..5d36971ad3 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherDataSource.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/DefaultDispatcherDataSource.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Dispatcher public IList
Addresses => _addresses; - public IList Endpoint { get; } = new List(); + public IList Endpoints => _endpoints; protected override IReadOnlyList
GetAddesses() => _addresses; diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs index aeb602ff3b..eeac61bf96 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs @@ -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 diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs index 60279ccb11..31e9ef190c 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherServiceCollectionExtensions.cs @@ -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(); + // Adds the EndpointMiddleare at the end of the pipeline if the DispatcherMiddleware is in use. + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + + // Adds a default dispatcher which will collect all data sources and endpoint selectors from DI. + services.TryAddEnumerable(ServiceDescriptor.Transient, DefaultDispatcherConfigureOptions>()); + services.AddSingleton(); services.AddSingleton(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + return services; } } diff --git a/src/Microsoft.AspNetCore.Dispatcher/EndpointHandlerFactoryBase.cs b/src/Microsoft.AspNetCore.Dispatcher/EndpointHandlerFactoryBase.cs new file mode 100644 index 0000000000..6aeadb855c --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/EndpointHandlerFactoryBase.cs @@ -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 +{ + /// + /// + /// Base class for implementations that can create a middleware-like delegate from an . + /// + /// + /// Implementations registered in the application services using the service type + /// will be automatically added to . + /// + /// + public abstract class EndpointHandlerFactoryBase + { + /// + /// Creates a middleware-like delegate for the provided . + /// + /// The that will execute for the current request. + /// An or null. + public abstract Func CreateHandler(Endpoint endpoint); + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/IDefaultDispatcherFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/IDefaultDispatcherFactory.cs new file mode 100644 index 0000000000..b8d1901b67 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/IDefaultDispatcherFactory.cs @@ -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 endpointSelectors); + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/TemplateEndpointHandlerFactory.cs b/src/Microsoft.AspNetCore.Dispatcher/TemplateEndpointHandlerFactory.cs new file mode 100644 index 0000000000..a3544d1e89 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/TemplateEndpointHandlerFactory.cs @@ -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 CreateHandler(Endpoint endpoint) + { + if (endpoint == null) + { + throw new ArgumentNullException(nameof(endpoint)); + } + + if (endpoint is TemplateEndpoint templateEndpoint) + { + return templateEndpoint.HandlerFactory; + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/Dispatcher/TreeDispatcherFactory.cs b/src/Microsoft.AspNetCore.Routing/Dispatcher/TreeDispatcherFactory.cs new file mode 100644 index 0000000000..a86183332b --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Dispatcher/TreeDispatcherFactory.cs @@ -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 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, + }; + } + } +}