Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit ca8136b

Browse files
committed
Compile middleware invoke method when extra args are provided
- Improves the performance when accessing scoped services in middleware
1 parent e7bf0e7 commit ca8136b

File tree

4 files changed

+231
-27
lines changed

4 files changed

+231
-27
lines changed

src/Microsoft.AspNet.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Linq;
6+
using System.Linq.Expressions;
67
using System.Reflection;
78
using System.Threading.Tasks;
89
using Microsoft.AspNet.Http;
@@ -16,7 +17,9 @@ namespace Microsoft.AspNet.Builder
1617
/// </summary>
1718
public static class UseMiddlewareExtensions
1819
{
19-
const string InvokeMethodName = "Invoke";
20+
private const string InvokeMethodName = "Invoke";
21+
22+
private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
2023

2124
/// <summary>
2225
/// Adds a middleware type to the application's request pipeline.
@@ -49,7 +52,7 @@ public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Ty
4952
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
5053
}
5154

52-
if (invokeMethods.Length == 0)
55+
if (invokeMethods.Length == 0)
5356
{
5457
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
5558
}
@@ -63,15 +66,20 @@ public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Ty
6366
var parameters = methodinfo.GetParameters();
6467
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
6568
{
66-
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,nameof(HttpContext)));
69+
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext)));
6770
}
6871

69-
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, new[] { next }.Concat(args).ToArray());
72+
var ctorArgs = new object[args.Length + 1];
73+
ctorArgs[0] = next;
74+
Array.Copy(args, 0, ctorArgs, 1, args.Length);
75+
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
7076
if (parameters.Length == 1)
7177
{
7278
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
7379
}
7480

81+
var factory = Compile<object>(methodinfo, parameters);
82+
7583
return context =>
7684
{
7785
var serviceProvider = context.RequestServices ?? applicationServices;
@@ -80,20 +88,97 @@ public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Ty
8088
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
8189
}
8290

83-
var arguments = new object[parameters.Length];
84-
arguments[0] = context;
85-
for(var index = 1; index != parameters.Length; ++index)
86-
{
87-
var serviceType = parameters[index].ParameterType;
88-
arguments[index] = serviceProvider.GetService(serviceType);
89-
if (arguments[index] == null)
90-
{
91-
throw new Exception(string.Format("No service for type '{0}' has been registered.", serviceType));
92-
}
93-
}
94-
return (Task)methodinfo.Invoke(instance, arguments);
91+
return factory(instance, context, serviceProvider);
9592
};
9693
});
9794
}
95+
96+
private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
97+
{
98+
99+
// If we call something like
100+
//
101+
// public class Middleware
102+
// {
103+
// public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory)
104+
// {
105+
//
106+
// }
107+
// }
108+
//
109+
110+
// We'll end up with something like this:
111+
// Generic version:
112+
//
113+
// Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
114+
// {
115+
// return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
116+
// }
117+
118+
// Non generic version:
119+
//
120+
// Task Invoke(object instance, HttpContext httpContext, IServiceprovider provider)
121+
// {
122+
// return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
123+
// }
124+
125+
// context =>
126+
// {
127+
// var serviceProvider = context.RequestServices ?? applicationServices;
128+
// if (serviceProvider == null)
129+
// {
130+
// throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
131+
// }
132+
//
133+
// return Invoke(httpContext, serviceProvider);
134+
// }
135+
136+
var middleware = typeof(T);
137+
138+
var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
139+
var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
140+
var instanceArg = Expression.Parameter(middleware, "middleware");
141+
142+
var methodArguments = new Expression[parameters.Length];
143+
methodArguments[0] = httpContextArg;
144+
for (int i = 1; i < parameters.Length; i++)
145+
{
146+
var parameterType = parameters[i].ParameterType;
147+
if (parameterType.IsByRef)
148+
{
149+
throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
150+
}
151+
152+
var parameterTypeExpression = new Expression[] {
153+
providerArg,
154+
Expression.Constant(parameterType, typeof(Type)),
155+
Expression.Constant(methodinfo.DeclaringType, typeof(Type))
156+
};
157+
methodArguments[i] = Expression.Convert(Expression.Call(GetServiceInfo, parameterTypeExpression), parameterType);
158+
}
159+
160+
Expression middlewareInstanceArg = instanceArg;
161+
if (methodinfo.DeclaringType != typeof(T))
162+
{
163+
middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType);
164+
}
165+
166+
var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments);
167+
168+
var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
169+
170+
return lambda.Compile();
171+
}
172+
173+
private static object GetService(IServiceProvider sp, Type type, Type middleware)
174+
{
175+
var service = sp.GetService(type);
176+
if (service == null)
177+
{
178+
throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
179+
}
180+
181+
return service;
182+
}
98183
}
99184
}

src/Microsoft.AspNet.Http.Abstractions/Properties/Resources.Designer.cs

Lines changed: 48 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.Http.Abstractions/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@
135135
<data name="Exception_PathMustStartWithSlash" xml:space="preserve">
136136
<value>The path in '{0}' must start with '/'.</value>
137137
</data>
138+
<data name="Exception_InvokeMiddlewareNoService" xml:space="preserve">
139+
<value>Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.</value>
140+
</data>
141+
<data name="Exception_InvokeDoesNotSupportRefOrOutParams" xml:space="preserve">
142+
<value>The '{0}' method must not have ref or out parameters.</value>
143+
</data>
138144
</root>

test/Microsoft.AspNet.Http.Abstractions.Tests/UseMiddlewareTest.cs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.AspNet.Builder;
77
using Microsoft.AspNet.Builder.Internal;
88
using Microsoft.AspNet.Http.Abstractions;
9+
using Microsoft.AspNet.Http.Internal;
910
using Xunit;
1011

1112
namespace Microsoft.AspNet.Http
@@ -20,7 +21,7 @@ public void UseMiddleware_WithNoParameters_ThrowsException()
2021
builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
2122
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
2223

23-
Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke",nameof(HttpContext)), exception.Message);
24+
Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke", nameof(HttpContext)), exception.Message);
2425
}
2526

2627
[Fact]
@@ -35,7 +36,7 @@ public void UseMiddleware_NonTaskReturnType_ThrowsException()
3536

3637
[Fact]
3738
public void UseMiddleware_NoInvokeMethod_ThrowsException()
38-
{
39+
{
3940
var mockServiceProvider = new DummyServiceProvider();
4041
var builder = new ApplicationBuilder(mockServiceProvider);
4142
builder.UseMiddleware(typeof(MiddlewareNoInvokeStub));
@@ -53,14 +54,86 @@ public void UseMiddleware_MutlipleInvokeMethods_ThrowsException()
5354
Assert.Equal(Resources.FormatException_UseMiddleMutlipleInvokes("Invoke"), exception.Message);
5455
}
5556

57+
[Fact]
58+
public async Task UseMiddleware_ThrowsIfArgCantBeResolvedFromContainer()
59+
{
60+
var mockServiceProvider = new DummyServiceProvider();
61+
var builder = new ApplicationBuilder(mockServiceProvider);
62+
builder.UseMiddleware(typeof(MiddlewareInjectInvokeNoService));
63+
var app = builder.Build();
64+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => app(new DefaultHttpContext()));
65+
Assert.Equal(Resources.FormatException_InvokeMiddlewareNoService(typeof(object), typeof(MiddlewareInjectInvokeNoService)), exception.Message);
66+
}
67+
68+
[Fact]
69+
public void UseMiddlewareWithInvokeArg()
70+
{
71+
var mockServiceProvider = new DummyServiceProvider();
72+
var builder = new ApplicationBuilder(mockServiceProvider);
73+
builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
74+
var app = builder.Build();
75+
app(new DefaultHttpContext());
76+
}
77+
78+
[Fact]
79+
public void UseMiddlewareWithIvokeWithOutAndRefThrows()
80+
{
81+
var mockServiceProvider = new DummyServiceProvider();
82+
var builder = new ApplicationBuilder(mockServiceProvider);
83+
builder.UseMiddleware(typeof(MiddlewareInjectWithOutAndRefParams));
84+
var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
85+
}
86+
5687
private class DummyServiceProvider : IServiceProvider
5788
{
5889
public object GetService(Type serviceType)
5990
{
91+
if (serviceType == typeof(IServiceProvider))
92+
{
93+
return this;
94+
}
6095
return null;
6196
}
6297
}
6398

99+
public class MiddlewareInjectWithOutAndRefParams
100+
{
101+
public MiddlewareInjectWithOutAndRefParams(RequestDelegate next)
102+
{
103+
}
104+
105+
public Task Invoke(HttpContext context, ref IServiceProvider sp1, out IServiceProvider sp2)
106+
{
107+
sp1 = null;
108+
sp2 = null;
109+
return Task.FromResult(0);
110+
}
111+
}
112+
113+
private class MiddlewareInjectInvokeNoService
114+
{
115+
public MiddlewareInjectInvokeNoService(RequestDelegate next)
116+
{
117+
}
118+
119+
public Task Invoke(HttpContext context, object value)
120+
{
121+
return Task.FromResult(0);
122+
}
123+
}
124+
125+
private class MiddlewareInjectInvoke
126+
{
127+
public MiddlewareInjectInvoke(RequestDelegate next)
128+
{
129+
}
130+
131+
public Task Invoke(HttpContext context, IServiceProvider provider)
132+
{
133+
return Task.FromResult(0);
134+
}
135+
}
136+
64137
private class MiddlewareNoParametersStub
65138
{
66139
public MiddlewareNoParametersStub(RequestDelegate next)
@@ -84,7 +157,7 @@ public int Invoke()
84157
return 0;
85158
}
86159
}
87-
160+
88161
private class MiddlewareNoInvokeStub
89162
{
90163
public MiddlewareNoInvokeStub(RequestDelegate next)

0 commit comments

Comments
 (0)