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:
Ryan Nowak 2017-09-29 16:06:38 -07:00 committed by GitHub
parent 9c30dd1256
commit abb41302e9
11 changed files with 250 additions and 59 deletions

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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,
};
}
}
}