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 IDefaultMatcherFactory _dispatcherFactory;
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
private readonly IEnumerable<EndpointHandlerFactoryBase> _handlerFactories;
private readonly IEnumerable<IHandlerFactory> _handlerFactories;
public DefaultDispatcherConfigureOptions(
IDefaultMatcherFactory dispatcherFactory,
IEnumerable<DispatcherDataSource> dataSources,
IEnumerable<EndpointSelector> endpointSelectors,
IEnumerable<EndpointHandlerFactoryBase> handlerFactories)
IEnumerable<IHandlerFactory> handlerFactories)
{
_dispatcherFactory = dispatcherFactory;
_dataSources = dataSources;
@ -33,12 +33,15 @@ namespace Microsoft.AspNetCore.Dispatcher
throw new ArgumentNullException(nameof(options));
}
options.Matchers.Add(_dispatcherFactory.CreateDispatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors));
foreach (var handlerFactory in _handlerFactories)
var matcher = _dispatcherFactory.CreateMatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors);
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.Values = context.Values;
// Associate this with the DispatcherEntry, not global
for (var i = 0; i < _options.HandlerFactories.Count; i++)
feature.Handler = entry.HandlerFactory.CreateHandler(feature.Endpoint);
if (feature.Handler == null)
{
var middleware = _options.HandlerFactories[i](feature.Endpoint);
if (middleware != null)
{
feature.Handler = middleware;
break;
}
throw new InvalidOperationException("Couldn't create a handler, that's bad.");
}
break;

View File

@ -8,7 +8,5 @@ namespace Microsoft.AspNetCore.Dispatcher
public class DispatcherOptions
{
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<TemplateAddressSelector>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointHandlerFactoryBase, TemplateEndpointHandlerFactory>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHandlerFactory, TemplateEndpointHandlerFactory>());
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
{
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>
/// <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>
/// Implementations registered in the application services using the service type <see cref="EndpointHandlerFactoryBase"/>
/// will be automatically added to <see cref="DispatcherOptions.HandlerFactories"/>.
/// Implementations registered in the application services using the service type <see cref="IHandlerFactory"/>
/// will be automatically added to set of handler factories used by the default dispatcher.
/// </para>
/// </summary>
public abstract class EndpointHandlerFactoryBase
public interface IHandlerFactory
{
/// <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);
Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint);
}
}

View File

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

View File

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

View File

@ -6,9 +6,9 @@ using Microsoft.AspNetCore.Http;
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)
{

View File

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

View File

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