Compile middleware invoke method when extra args are provided
- Improves the performance when accessing scoped services in middleware
This commit is contained in:
parent
e7bf0e71bb
commit
ca8136b73c
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
@ -16,7 +17,9 @@ namespace Microsoft.AspNet.Builder
|
|||
/// </summary>
|
||||
public static class UseMiddlewareExtensions
|
||||
{
|
||||
const string InvokeMethodName = "Invoke";
|
||||
private const string InvokeMethodName = "Invoke";
|
||||
|
||||
private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a middleware type to the application's request pipeline.
|
||||
|
|
@ -49,7 +52,7 @@ namespace Microsoft.AspNet.Builder
|
|||
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
|
||||
}
|
||||
|
||||
if (invokeMethods.Length == 0)
|
||||
if (invokeMethods.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
|
||||
}
|
||||
|
|
@ -63,15 +66,20 @@ namespace Microsoft.AspNet.Builder
|
|||
var parameters = methodinfo.GetParameters();
|
||||
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,nameof(HttpContext)));
|
||||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext)));
|
||||
}
|
||||
|
||||
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, new[] { next }.Concat(args).ToArray());
|
||||
var ctorArgs = new object[args.Length + 1];
|
||||
ctorArgs[0] = next;
|
||||
Array.Copy(args, 0, ctorArgs, 1, args.Length);
|
||||
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
|
||||
if (parameters.Length == 1)
|
||||
{
|
||||
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
|
||||
}
|
||||
|
||||
var factory = Compile<object>(methodinfo, parameters);
|
||||
|
||||
return context =>
|
||||
{
|
||||
var serviceProvider = context.RequestServices ?? applicationServices;
|
||||
|
|
@ -80,20 +88,97 @@ namespace Microsoft.AspNet.Builder
|
|||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
|
||||
}
|
||||
|
||||
var arguments = new object[parameters.Length];
|
||||
arguments[0] = context;
|
||||
for(var index = 1; index != parameters.Length; ++index)
|
||||
{
|
||||
var serviceType = parameters[index].ParameterType;
|
||||
arguments[index] = serviceProvider.GetService(serviceType);
|
||||
if (arguments[index] == null)
|
||||
{
|
||||
throw new Exception(string.Format("No service for type '{0}' has been registered.", serviceType));
|
||||
}
|
||||
}
|
||||
return (Task)methodinfo.Invoke(instance, arguments);
|
||||
return factory(instance, context, serviceProvider);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
|
||||
{
|
||||
|
||||
// If we call something like
|
||||
//
|
||||
// public class Middleware
|
||||
// {
|
||||
// public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
// We'll end up with something like this:
|
||||
// Generic version:
|
||||
//
|
||||
// Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
|
||||
// {
|
||||
// return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
|
||||
// }
|
||||
|
||||
// Non generic version:
|
||||
//
|
||||
// Task Invoke(object instance, HttpContext httpContext, IServiceprovider provider)
|
||||
// {
|
||||
// return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
|
||||
// }
|
||||
|
||||
// context =>
|
||||
// {
|
||||
// var serviceProvider = context.RequestServices ?? applicationServices;
|
||||
// if (serviceProvider == null)
|
||||
// {
|
||||
// throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
|
||||
// }
|
||||
//
|
||||
// return Invoke(httpContext, serviceProvider);
|
||||
// }
|
||||
|
||||
var middleware = typeof(T);
|
||||
|
||||
var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
|
||||
var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
|
||||
var instanceArg = Expression.Parameter(middleware, "middleware");
|
||||
|
||||
var methodArguments = new Expression[parameters.Length];
|
||||
methodArguments[0] = httpContextArg;
|
||||
for (int i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameterType = parameters[i].ParameterType;
|
||||
if (parameterType.IsByRef)
|
||||
{
|
||||
throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
|
||||
}
|
||||
|
||||
var parameterTypeExpression = new Expression[] {
|
||||
providerArg,
|
||||
Expression.Constant(parameterType, typeof(Type)),
|
||||
Expression.Constant(methodinfo.DeclaringType, typeof(Type))
|
||||
};
|
||||
methodArguments[i] = Expression.Convert(Expression.Call(GetServiceInfo, parameterTypeExpression), parameterType);
|
||||
}
|
||||
|
||||
Expression middlewareInstanceArg = instanceArg;
|
||||
if (methodinfo.DeclaringType != typeof(T))
|
||||
{
|
||||
middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType);
|
||||
}
|
||||
|
||||
var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments);
|
||||
|
||||
var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
|
||||
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
private static object GetService(IServiceProvider sp, Type type, Type middleware)
|
||||
{
|
||||
var service = sp.GetService(type);
|
||||
if (service == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,14 +10,6 @@ namespace Microsoft.AspNet.Http.Abstractions
|
|||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Http.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' is not available.
|
||||
/// </summary>
|
||||
internal static string FormatException_PathMustStartWithSlash(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_PathMustStartWithSlash"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' is not available.
|
||||
/// </summary>
|
||||
|
|
@ -98,6 +90,54 @@ namespace Microsoft.AspNet.Http.Abstractions
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The path in '{0}' must start with '/'.
|
||||
/// </summary>
|
||||
internal static string Exception_PathMustStartWithSlash
|
||||
{
|
||||
get { return GetString("Exception_PathMustStartWithSlash"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The path in '{0}' must start with '/'.
|
||||
/// </summary>
|
||||
internal static string FormatException_PathMustStartWithSlash(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_PathMustStartWithSlash"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.
|
||||
/// </summary>
|
||||
internal static string Exception_InvokeMiddlewareNoService
|
||||
{
|
||||
get { return GetString("Exception_InvokeMiddlewareNoService"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatException_InvokeMiddlewareNoService(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeMiddlewareNoService"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' method must not have ref or out parameters.
|
||||
/// </summary>
|
||||
internal static string Exception_InvokeDoesNotSupportRefOrOutParams
|
||||
{
|
||||
get { return GetString("Exception_InvokeDoesNotSupportRefOrOutParams"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' method must not have ref or out parameters.
|
||||
/// </summary>
|
||||
internal static string FormatException_InvokeDoesNotSupportRefOrOutParams(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeDoesNotSupportRefOrOutParams"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -135,4 +135,10 @@
|
|||
<data name="Exception_PathMustStartWithSlash" xml:space="preserve">
|
||||
<value>The path in '{0}' must start with '/'.</value>
|
||||
</data>
|
||||
<data name="Exception_InvokeMiddlewareNoService" xml:space="preserve">
|
||||
<value>Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.</value>
|
||||
</data>
|
||||
<data name="Exception_InvokeDoesNotSupportRefOrOutParams" xml:space="preserve">
|
||||
<value>The '{0}' method must not have ref or out parameters.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Builder.Internal;
|
||||
using Microsoft.AspNet.Http.Abstractions;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Http
|
||||
|
|
@ -20,7 +21,7 @@ namespace Microsoft.AspNet.Http
|
|||
builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
|
||||
|
||||
Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke",nameof(HttpContext)), exception.Message);
|
||||
Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke", nameof(HttpContext)), exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -35,7 +36,7 @@ namespace Microsoft.AspNet.Http
|
|||
|
||||
[Fact]
|
||||
public void UseMiddleware_NoInvokeMethod_ThrowsException()
|
||||
{
|
||||
{
|
||||
var mockServiceProvider = new DummyServiceProvider();
|
||||
var builder = new ApplicationBuilder(mockServiceProvider);
|
||||
builder.UseMiddleware(typeof(MiddlewareNoInvokeStub));
|
||||
|
|
@ -53,14 +54,86 @@ namespace Microsoft.AspNet.Http
|
|||
Assert.Equal(Resources.FormatException_UseMiddleMutlipleInvokes("Invoke"), exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseMiddleware_ThrowsIfArgCantBeResolvedFromContainer()
|
||||
{
|
||||
var mockServiceProvider = new DummyServiceProvider();
|
||||
var builder = new ApplicationBuilder(mockServiceProvider);
|
||||
builder.UseMiddleware(typeof(MiddlewareInjectInvokeNoService));
|
||||
var app = builder.Build();
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => app(new DefaultHttpContext()));
|
||||
Assert.Equal(Resources.FormatException_InvokeMiddlewareNoService(typeof(object), typeof(MiddlewareInjectInvokeNoService)), exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseMiddlewareWithInvokeArg()
|
||||
{
|
||||
var mockServiceProvider = new DummyServiceProvider();
|
||||
var builder = new ApplicationBuilder(mockServiceProvider);
|
||||
builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
|
||||
var app = builder.Build();
|
||||
app(new DefaultHttpContext());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseMiddlewareWithIvokeWithOutAndRefThrows()
|
||||
{
|
||||
var mockServiceProvider = new DummyServiceProvider();
|
||||
var builder = new ApplicationBuilder(mockServiceProvider);
|
||||
builder.UseMiddleware(typeof(MiddlewareInjectWithOutAndRefParams));
|
||||
var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
|
||||
}
|
||||
|
||||
private class DummyServiceProvider : IServiceProvider
|
||||
{
|
||||
public object GetService(Type serviceType)
|
||||
{
|
||||
if (serviceType == typeof(IServiceProvider))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class MiddlewareInjectWithOutAndRefParams
|
||||
{
|
||||
public MiddlewareInjectWithOutAndRefParams(RequestDelegate next)
|
||||
{
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context, ref IServiceProvider sp1, out IServiceProvider sp2)
|
||||
{
|
||||
sp1 = null;
|
||||
sp2 = null;
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
private class MiddlewareInjectInvokeNoService
|
||||
{
|
||||
public MiddlewareInjectInvokeNoService(RequestDelegate next)
|
||||
{
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context, object value)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
private class MiddlewareInjectInvoke
|
||||
{
|
||||
public MiddlewareInjectInvoke(RequestDelegate next)
|
||||
{
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context, IServiceProvider provider)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
private class MiddlewareNoParametersStub
|
||||
{
|
||||
public MiddlewareNoParametersStub(RequestDelegate next)
|
||||
|
|
@ -84,7 +157,7 @@ namespace Microsoft.AspNet.Http
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class MiddlewareNoInvokeStub
|
||||
{
|
||||
public MiddlewareNoInvokeStub(RequestDelegate next)
|
||||
|
|
|
|||
Loading…
Reference in New Issue