Respond to feedback about handler factory

- Handler factories are now associated with the dispatcher entry
- Handler factory is now an interface
This commit is contained in:
Ryan Nowak 2017-10-05 22:25:49 -07:00
parent 63d2cc4637
commit 56ae2e0177
14 changed files with 118 additions and 68 deletions

View File

@ -1,17 +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 Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
/// <summary>
/// A delegate which attempts to create a <see cref="Func{RequestDelegate, RequestDelegate}"/> for the selected <see cref="Endpoint"/>.
/// </summary>
/// <param name="endpoint">The <see cref="Endpoint"/> selected by the dispatcher.</param>
/// <returns>
/// A <see cref="Func{RequestDelegate, RequestDelegate}"/> that invokes the operation represented by the <see cref="Endpoint"/>, or <c>null</c>.
/// </returns>
public delegate Func<RequestDelegate, RequestDelegate> EndpointHandlerFactory(Endpoint endpoint);
}

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 System.Linq;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
public class CompositeHandlerFactory : IHandlerFactory
{
private readonly IHandlerFactory[] _factories;
public CompositeHandlerFactory(IEnumerable<IHandlerFactory> factories)
{
if (factories == null)
{
throw new ArgumentNullException(nameof(factories));
}
_factories = factories.ToArray();
}
public Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
{
if (endpoint == null)
{
throw new ArgumentNullException(nameof(endpoint));
}
for (var i = 0; i < _factories.Length; i++)
{
var handler = _factories[i].CreateHandler(endpoint);
if (handler != null)
{
return handler;
}
}
return null;
}
}
}

View File

@ -12,13 +12,13 @@ namespace Microsoft.AspNetCore.Dispatcher
private readonly IEnumerable<DispatcherDataSource> _dataSources; private readonly IEnumerable<DispatcherDataSource> _dataSources;
private readonly IDefaultMatcherFactory _dispatcherFactory; private readonly IDefaultMatcherFactory _dispatcherFactory;
private readonly IEnumerable<EndpointSelector> _endpointSelectors; private readonly IEnumerable<EndpointSelector> _endpointSelectors;
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories; private readonly IEnumerable<IHandlerFactory> _handlerFactories;
public DefaultDispatcherConfigureOptions( public DefaultDispatcherConfigureOptions(
IDefaultMatcherFactory dispatcherFactory, IDefaultMatcherFactory dispatcherFactory,
IEnumerable<DispatcherDataSource> dataSources, IEnumerable<DispatcherDataSource> dataSources,
IEnumerable<EndpointSelector> endpointSelectors, IEnumerable<EndpointSelector> endpointSelectors,
IEnumerable<EndpointHandlerFactoryBase> handlerFactories) IEnumerable<IHandlerFactory> handlerFactories)
{ {
_dispatcherFactory = dispatcherFactory; _dispatcherFactory = dispatcherFactory;
_dataSources = dataSources; _dataSources = dataSources;
@ -33,12 +33,15 @@ namespace Microsoft.AspNetCore.Dispatcher
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
options.Matchers.Add(_dispatcherFactory.CreateDispatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors)); var matcher = _dispatcherFactory.CreateMatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors);
foreach (var handlerFactory in _handlerFactories) options.Matchers.Add(new MatcherEntry()
{ {
options.HandlerFactories.Add(handlerFactory.CreateHandler); Matcher = matcher,
} AddressProvider = matcher as IAddressCollectionProvider,
EndpointProvider = matcher as IEndpointCollectionProvider,
HandlerFactory = new CompositeHandlerFactory(_handlerFactories),
});
} }
} }
} }

View File

@ -63,15 +63,10 @@ namespace Microsoft.AspNetCore.Dispatcher
feature.Endpoint = context.Endpoint; feature.Endpoint = context.Endpoint;
feature.Values = context.Values; feature.Values = context.Values;
// Associate this with the DispatcherEntry, not global feature.Handler = entry.HandlerFactory.CreateHandler(feature.Endpoint);
for (var i = 0; i < _options.HandlerFactories.Count; i++) if (feature.Handler == null)
{ {
var middleware = _options.HandlerFactories[i](feature.Endpoint); throw new InvalidOperationException("Couldn't create a handler, that's bad.");
if (middleware != null)
{
feature.Handler = middleware;
break;
}
} }
break; break;

View File

@ -8,7 +8,5 @@ namespace Microsoft.AspNetCore.Dispatcher
public class DispatcherOptions public class DispatcherOptions
{ {
public MatcherCollection Matchers { get; } = new MatcherCollection(); public MatcherCollection Matchers { get; } = new MatcherCollection();
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
} }
} }

View File

@ -27,7 +27,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<AddressTable, DefaultAddressTable>(); services.AddSingleton<AddressTable, DefaultAddressTable>();
services.AddSingleton<TemplateAddressSelector>(); services.AddSingleton<TemplateAddressSelector>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointHandlerFactoryBase, TemplateEndpointHandlerFactory>()); services.TryAddEnumerable(ServiceDescriptor.Singleton<IHandlerFactory, TemplateEndpointHandlerFactory>());
return services; return services;
} }

View File

@ -0,0 +1,42 @@
// 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 simple implementation of <see cref="IHandlerFactory"/> that adapts a particular endpoint
/// type to a handler.
/// </summary>
public sealed class HandlerFactory<TEndpoint> : IHandlerFactory
{
private readonly Func<TEndpoint, Func<RequestDelegate, RequestDelegate>> _adapter;
public HandlerFactory(Func<TEndpoint, Func<RequestDelegate, RequestDelegate>> adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
_adapter = adapter;
}
public Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
{
if (endpoint == null)
{
throw new ArgumentNullException(nameof(endpoint));
}
if (endpoint is TEndpoint myTypeOfEndpoint)
{
return _adapter(myTypeOfEndpoint);
}
return null;
}
}
}

View File

@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Dispatcher
{ {
public interface IDefaultMatcherFactory public interface IDefaultMatcherFactory
{ {
MatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors); IMatcher CreateMatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors);
} }
} }

View File

@ -8,20 +8,20 @@ namespace Microsoft.AspNetCore.Dispatcher
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// Base class for implementations that can create a middleware-like delegate from an <see cref="Endpoint"/>. /// An interface for components that can create a middleware-like delegate from an <see cref="Endpoint"/>.
/// </para> /// </para>
/// <para> /// <para>
/// Implementations registered in the application services using the service type <see cref="EndpointHandlerFactoryBase"/> /// Implementations registered in the application services using the service type <see cref="IHandlerFactory"/>
/// will be automatically added to <see cref="DispatcherOptions.HandlerFactories"/>. /// will be automatically added to set of handler factories used by the default dispatcher.
/// </para> /// </para>
/// </summary> /// </summary>
public abstract class EndpointHandlerFactoryBase public interface IHandlerFactory
{ {
/// <summary> /// <summary>
/// Creates a middleware-like delegate for the provided <see cref="Endpoint"/>. /// Creates a middleware-like delegate for the provided <see cref="Endpoint"/>.
/// </summary> /// </summary>
/// <param name="endpoint">The <see cref="Endpoint"/> that will execute for the current request.</param> /// <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> /// <returns>An <see cref="Func{RequestDelegate, RequestDelegate}"/> or <c>null</c>.</returns>
public abstract Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint); Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint);
} }
} }

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Dispatcher
{ {
public class MatcherCollection : Collection<MatcherEntry> public class MatcherCollection : Collection<MatcherEntry>
{ {
public void Add(MatcherBase matcher) public void Add(IMatcher matcher, IHandlerFactory handerFactory)
{ {
if (matcher == null) if (matcher == null)
{ {
@ -18,21 +18,10 @@ namespace Microsoft.AspNetCore.Dispatcher
Add(new MatcherEntry() Add(new MatcherEntry()
{ {
Matcher = matcher, Matcher = matcher,
AddressProvider = matcher, AddressProvider = matcher as IAddressCollectionProvider,
EndpointProvider = matcher, EndpointProvider = matcher as IEndpointCollectionProvider,
});
}
public void Add(IMatcher matcher) HandlerFactory = handerFactory,
{
if (matcher == null)
{
throw new ArgumentNullException(nameof(matcher));
}
Add(new MatcherEntry()
{
Matcher = matcher,
}); });
} }
} }

View File

@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Dispatcher
{ {
public class MatcherEntry public class MatcherEntry
{ {
public IHandlerFactory HandlerFactory { get; set; }
public IMatcher Matcher { get; set; } public IMatcher Matcher { get; set; }
public IAddressCollectionProvider AddressProvider { get; set; } public IAddressCollectionProvider AddressProvider { get; set; }

View File

@ -6,9 +6,9 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher namespace Microsoft.AspNetCore.Dispatcher
{ {
public class TemplateEndpointHandlerFactory : EndpointHandlerFactoryBase public sealed class TemplateEndpointHandlerFactory : IHandlerFactory
{ {
public override Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint) public Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
{ {
if (endpoint == null) if (endpoint == null)
{ {

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
{ {
public class TreeMatcherFactory : IDefaultMatcherFactory public class TreeMatcherFactory : IDefaultMatcherFactory
{ {
public MatcherEntry CreateDispatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors) public IMatcher CreateMatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors)
{ {
if (dataSource == null) if (dataSource == null)
{ {
@ -26,12 +26,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
matcher.Selectors.Add(endpointSelector); matcher.Selectors.Add(endpointSelector);
} }
return new MatcherEntry() return matcher;
{
AddressProvider = matcher,
Matcher = matcher,
EndpointProvider = matcher,
};
} }
} }
} }

View File

@ -72,9 +72,8 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
Selectors = Selectors =
{ {
new HttpMethodEndpointSelector(), new HttpMethodEndpointSelector(),
} },
}); }, new TemplateEndpointHandlerFactory());
options.HandlerFactories.Add(endpoint => (endpoint as TemplateEndpoint)?.HandlerFactory);
} }
private Task Products_Fallback(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Fallback"); private Task Products_Fallback(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Fallback");