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

Commit b7d9e11

Browse files
committed
Middleware invokation with per-request services
* Extension methods for .Use<TService1, ...> and .Run<TService1, ...> add service parameters to lambda * Middleware class .Invoke method may take services as additional parameters
1 parent 02aa1c5 commit b7d9e11

File tree

4 files changed

+315
-2
lines changed

4 files changed

+315
-2
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Linq;
66
using System.Reflection;
77
using Microsoft.Framework.DependencyInjection;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNet.Http;
810

911
namespace Microsoft.AspNet.Builder
1012
{
@@ -17,12 +19,41 @@ public static IApplicationBuilder UseMiddleware<T>(this IApplicationBuilder buil
1719

1820
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder builder, Type middleware, params object[] args)
1921
{
22+
var applicationServices = builder.ApplicationServices;
2023
return builder.Use(next =>
2124
{
22-
var typeActivator = builder.ApplicationServices.GetRequiredService<ITypeActivator>();
25+
var typeActivator = applicationServices.GetRequiredService<ITypeActivator>();
2326
var instance = typeActivator.CreateInstance(builder.ApplicationServices, middleware, new[] { next }.Concat(args).ToArray());
2427
var methodinfo = middleware.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public);
25-
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
28+
var parameters = methodinfo.GetParameters();
29+
if (parameters[0].ParameterType != typeof(HttpContext))
30+
{
31+
throw new Exception("TODO: Middleware Invoke method must take first argument of HttpContext");
32+
}
33+
if (parameters.Length == 1)
34+
{
35+
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
36+
}
37+
return context =>
38+
{
39+
var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices;
40+
if (serviceProvider == null)
41+
{
42+
throw new Exception("TODO: IServiceProvider is not available");
43+
}
44+
var arguments = new object[parameters.Length];
45+
arguments[0] = context;
46+
for(var index = 1; index != parameters.Length; ++index)
47+
{
48+
var serviceType = parameters[index].ParameterType;
49+
arguments[index] = serviceProvider.GetService(serviceType);
50+
if (arguments[index] == null)
51+
{
52+
throw new Exception(string.Format("TODO: No service for type '{0}' has been registered.", serviceType));
53+
}
54+
}
55+
return (Task)methodinfo.Invoke(instance, arguments);
56+
};
2657
});
2758
}
2859
}

src/Microsoft.AspNet.Http/Extensions/RunExtensions.cs

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

44
using System;
55
using Microsoft.AspNet.Http;
6+
using System.Threading.Tasks;
67

78
namespace Microsoft.AspNet.Builder
89
{
@@ -12,5 +13,25 @@ public static void Run([NotNull] this IApplicationBuilder app, [NotNull] Request
1213
{
1314
app.Use(_ => handler);
1415
}
16+
17+
public static void Run<TService1>(this IApplicationBuilder app, Func<HttpContext, TService1, Task> handler)
18+
{
19+
app.Use<TService1>((ctx, _, s1) => handler(ctx, s1));
20+
}
21+
22+
public static void Run<TService1, TService2>(this IApplicationBuilder app, Func<HttpContext, TService1, TService2, Task> handler)
23+
{
24+
app.Use<TService1, TService2>((ctx, _, s1, s2) => handler(ctx, s1, s2));
25+
}
26+
27+
public static void Run<TService1, TService2, TService3>(this IApplicationBuilder app, Func<HttpContext, TService1, TService2, TService3, Task> handler)
28+
{
29+
app.Use<TService1, TService2, TService3>((ctx, _, s1, s2, s3) => handler(ctx, s1, s2, s3));
30+
}
31+
32+
public static void Run<TService1, TService2, TService3, TService4>(this IApplicationBuilder app, Func<HttpContext, TService1, TService2, TService3, TService4, Task> handler)
33+
{
34+
app.Use<TService1, TService2, TService3, TService4>((ctx, _, s1, s2, s3, s4) => handler(ctx, s1, s2, s3, s4));
35+
}
1536
}
1637
}

src/Microsoft.AspNet.Http/Extensions/UseExtensions.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,125 @@ public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpCon
2626
};
2727
});
2828
}
29+
30+
/// <summary>
31+
/// Use middleware defined in-line
32+
/// </summary>
33+
/// <typeparam name="TService1">Per-request service required by middleware</typeparam>
34+
/// <param name="app"></param>
35+
/// <param name="middleware">A function that handles the request or calls the given next function.</param>
36+
/// <returns></returns>
37+
public static IApplicationBuilder Use<TService1>(this IApplicationBuilder app, Func<HttpContext, Func<Task>, TService1, Task> middleware)
38+
{
39+
var applicationServices = app.ApplicationServices;
40+
return app.Use(next => context =>
41+
{
42+
var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices;
43+
if (serviceProvider == null)
44+
{
45+
throw new Exception("TODO: IServiceProvider is not available");
46+
}
47+
return middleware(
48+
context,
49+
() => next(context),
50+
GetRequiredService<TService1>(serviceProvider));
51+
});
52+
}
53+
54+
/// <summary>
55+
/// Use middleware defined in-line
56+
/// </summary>
57+
/// <typeparam name="TService1">Per-request service required by middleware</typeparam>
58+
/// <typeparam name="TService2">Per-request service required by middleware</typeparam>
59+
/// <param name="app"></param>
60+
/// <param name="middleware">A function that handles the request or calls the given next function.</param>
61+
/// <returns></returns>
62+
public static IApplicationBuilder Use<TService1, TService2>(this IApplicationBuilder app, Func<HttpContext, Func<Task>, TService1, TService2, Task> middleware)
63+
{
64+
var applicationServices = app.ApplicationServices;
65+
return app.Use(next => context =>
66+
{
67+
var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices;
68+
if (serviceProvider == null)
69+
{
70+
throw new Exception("TODO: IServiceProvider is not available");
71+
}
72+
return middleware(
73+
context,
74+
() => next(context),
75+
GetRequiredService<TService1>(serviceProvider),
76+
GetRequiredService<TService2>(serviceProvider));
77+
});
78+
}
79+
80+
/// <summary>
81+
/// Use middleware defined in-line
82+
/// </summary>
83+
/// <typeparam name="TService1">Per-request service required by middleware</typeparam>
84+
/// <typeparam name="TService2">Per-request service required by middleware</typeparam>
85+
/// <typeparam name="TService3">Per-request service required by middleware</typeparam>
86+
/// <param name="app"></param>
87+
/// <param name="middleware">A function that handles the request or calls the given next function.</param>
88+
/// <returns></returns>
89+
public static IApplicationBuilder Use<TService1, TService2, TService3>(this IApplicationBuilder app, Func<HttpContext, Func<Task>, TService1, TService2, TService3, Task> middleware)
90+
{
91+
var applicationServices = app.ApplicationServices;
92+
return app.Use(next => context =>
93+
{
94+
var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices;
95+
if (serviceProvider == null)
96+
{
97+
throw new Exception("TODO: IServiceProvider is not available");
98+
}
99+
return middleware(
100+
context,
101+
() => next(context),
102+
GetRequiredService<TService1>(serviceProvider),
103+
GetRequiredService<TService2>(serviceProvider),
104+
GetRequiredService<TService3>(serviceProvider));
105+
});
106+
}
107+
108+
/// <summary>
109+
/// Use middleware defined in-line
110+
/// </summary>
111+
/// <typeparam name="TService1">Per-request service required by middleware</typeparam>
112+
/// <typeparam name="TService2">Per-request service required by middleware</typeparam>
113+
/// <typeparam name="TService3">Per-request service required by middleware</typeparam>
114+
/// <typeparam name="TService4">Per-request service required by middleware</typeparam>
115+
/// <param name="app"></param>
116+
/// <param name="middleware">A function that handles the request or calls the given next function.</param>
117+
/// <returns></returns>
118+
public static IApplicationBuilder Use<TService1, TService2, TService3, TService4>(this IApplicationBuilder app, Func<HttpContext, Func<Task>, TService1, TService2, TService3, TService4, Task> middleware)
119+
{
120+
var applicationServices = app.ApplicationServices;
121+
return app.Use(next => context =>
122+
{
123+
var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices;
124+
if (serviceProvider == null)
125+
{
126+
throw new Exception("TODO: IServiceProvider is not available");
127+
}
128+
return middleware(
129+
context,
130+
() => next(context),
131+
GetRequiredService<TService1>(serviceProvider),
132+
GetRequiredService<TService2>(serviceProvider),
133+
GetRequiredService<TService3>(serviceProvider),
134+
GetRequiredService<TService4>(serviceProvider));
135+
});
136+
}
137+
138+
private static TService GetRequiredService<TService>(IServiceProvider serviceProvider)
139+
{
140+
var service = (TService)serviceProvider.GetService(typeof(TService));
141+
142+
if (service == null)
143+
{
144+
throw new Exception(string.Format("TODO: No service for type '{0}' has been registered.", typeof(TService)));
145+
}
146+
147+
return service;
148+
}
29149
}
30150
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Xunit;
6+
using Microsoft.AspNet.Builder;
7+
using Microsoft.Framework.DependencyInjection;
8+
using Microsoft.Framework.DependencyInjection.Fallback;
9+
using Microsoft.AspNet.PipelineCore;
10+
using System.Collections.Generic;
11+
using System.Threading.Tasks;
12+
13+
namespace Microsoft.AspNet.Http.Extensions.Tests
14+
{
15+
public class UseWithServicesTests
16+
{
17+
[Fact]
18+
public async Task CallingUseThatAlsoTakesServices()
19+
{
20+
var builder = new ApplicationBuilder(new ServiceCollection()
21+
.AddScoped<ITestService, TestService>()
22+
.BuildServiceProvider());
23+
24+
ITestService theService = null;
25+
builder.Use<ITestService>(async (ctx, next, testService) =>
26+
{
27+
theService = testService;
28+
await next();
29+
});
30+
31+
var app = builder.Build();
32+
await app(new DefaultHttpContext());
33+
34+
Assert.IsType<TestService>(theService);
35+
}
36+
37+
[Fact]
38+
public async Task ServicesArePerRequest()
39+
{
40+
var services = new ServiceCollection()
41+
.AddScoped<ITestService, TestService>()
42+
.AddTransient<ITypeActivator, TypeActivator>()
43+
.BuildServiceProvider();
44+
var builder = new ApplicationBuilder(services);
45+
46+
builder.Use(async (ctx, next) =>
47+
{
48+
var serviceScopeFactory = services.GetRequiredService<IServiceScopeFactory>();
49+
using (var serviceScope = serviceScopeFactory.CreateScope())
50+
{
51+
var priorApplicationServices = ctx.ApplicationServices;
52+
var priorRequestServices = ctx.ApplicationServices;
53+
ctx.ApplicationServices = services;
54+
ctx.RequestServices = serviceScope.ServiceProvider;
55+
try
56+
{
57+
await next();
58+
}
59+
finally
60+
{
61+
ctx.ApplicationServices = priorApplicationServices;
62+
ctx.RequestServices = priorRequestServices;
63+
}
64+
}
65+
});
66+
67+
var testServicesA = new List<ITestService>();
68+
builder.Use(async (HttpContext ctx, Func<Task> next, ITestService testService) =>
69+
{
70+
testServicesA.Add(testService);
71+
await next();
72+
});
73+
74+
var testServicesB = new List<ITestService>();
75+
builder.Use<ITestService>(async (ctx, next, testService) =>
76+
{
77+
testServicesB.Add(testService);
78+
await next();
79+
});
80+
81+
var app = builder.Build();
82+
await app(new DefaultHttpContext());
83+
await app(new DefaultHttpContext());
84+
85+
Assert.Equal(2, testServicesA.Count);
86+
Assert.IsType<TestService>(testServicesA[0]);
87+
Assert.IsType<TestService>(testServicesA[1]);
88+
89+
Assert.Equal(2, testServicesB.Count);
90+
Assert.IsType<TestService>(testServicesB[0]);
91+
Assert.IsType<TestService>(testServicesB[1]);
92+
93+
Assert.Same(testServicesA[0], testServicesB[0]);
94+
Assert.Same(testServicesA[1], testServicesB[1]);
95+
96+
Assert.NotSame(testServicesA[0], testServicesA[1]);
97+
Assert.NotSame(testServicesB[0], testServicesB[1]);
98+
}
99+
100+
[Fact]
101+
public async Task InvokeMethodWillAllowPerRequestServices()
102+
{
103+
var services = new ServiceCollection()
104+
.AddScoped<ITestService, TestService>()
105+
.AddTransient<ITypeActivator, TypeActivator>()
106+
.BuildServiceProvider();
107+
var builder = new ApplicationBuilder(services);
108+
builder.UseMiddleware<TestMiddleware>();
109+
var app = builder.Build();
110+
111+
var ctx1 = new DefaultHttpContext();
112+
await app(ctx1);
113+
114+
var testService = ctx1.Items[typeof(ITestService)];
115+
Assert.IsType<TestService>(testService);
116+
}
117+
}
118+
119+
public interface ITestService
120+
{
121+
}
122+
123+
public class TestService : ITestService
124+
{
125+
}
126+
127+
public class TestMiddleware
128+
{
129+
RequestDelegate _next;
130+
131+
public TestMiddleware(RequestDelegate next)
132+
{
133+
_next = next;
134+
}
135+
136+
public async Task Invoke(HttpContext context, ITestService testService)
137+
{
138+
context.Items[typeof(ITestService)] = testService;
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)