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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
@ -16,7 +17,9 @@ namespace Microsoft.AspNet.Builder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class UseMiddlewareExtensions
|
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>
|
/// <summary>
|
||||||
/// Adds a middleware type to the application's request pipeline.
|
/// 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));
|
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invokeMethods.Length == 0)
|
if (invokeMethods.Length == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
|
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
|
||||||
}
|
}
|
||||||
|
|
@ -63,15 +66,20 @@ namespace Microsoft.AspNet.Builder
|
||||||
var parameters = methodinfo.GetParameters();
|
var parameters = methodinfo.GetParameters();
|
||||||
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
|
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)
|
if (parameters.Length == 1)
|
||||||
{
|
{
|
||||||
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
|
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var factory = Compile<object>(methodinfo, parameters);
|
||||||
|
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
var serviceProvider = context.RequestServices ?? applicationServices;
|
var serviceProvider = context.RequestServices ?? applicationServices;
|
||||||
|
|
@ -80,20 +88,97 @@ namespace Microsoft.AspNet.Builder
|
||||||
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
|
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var arguments = new object[parameters.Length];
|
return factory(instance, context, serviceProvider);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
private static readonly ResourceManager _resourceManager
|
||||||
= new ResourceManager("Microsoft.AspNet.Http.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
= 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>
|
/// <summary>
|
||||||
/// '{0}' is not available.
|
/// '{0}' is not available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -98,6 +90,54 @@ namespace Microsoft.AspNet.Http.Abstractions
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0);
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -135,4 +135,10 @@
|
||||||
<data name="Exception_PathMustStartWithSlash" xml:space="preserve">
|
<data name="Exception_PathMustStartWithSlash" xml:space="preserve">
|
||||||
<value>The path in '{0}' must start with '/'.</value>
|
<value>The path in '{0}' must start with '/'.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.Builder.Internal;
|
using Microsoft.AspNet.Builder.Internal;
|
||||||
using Microsoft.AspNet.Http.Abstractions;
|
using Microsoft.AspNet.Http.Abstractions;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Http
|
namespace Microsoft.AspNet.Http
|
||||||
|
|
@ -20,7 +21,7 @@ namespace Microsoft.AspNet.Http
|
||||||
builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
|
builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
|
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]
|
[Fact]
|
||||||
|
|
@ -53,14 +54,86 @@ namespace Microsoft.AspNet.Http
|
||||||
Assert.Equal(Resources.FormatException_UseMiddleMutlipleInvokes("Invoke"), exception.Message);
|
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
|
private class DummyServiceProvider : IServiceProvider
|
||||||
{
|
{
|
||||||
public object GetService(Type serviceType)
|
public object GetService(Type serviceType)
|
||||||
{
|
{
|
||||||
|
if (serviceType == typeof(IServiceProvider))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
return null;
|
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
|
private class MiddlewareNoParametersStub
|
||||||
{
|
{
|
||||||
public MiddlewareNoParametersStub(RequestDelegate next)
|
public MiddlewareNoParametersStub(RequestDelegate next)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue