diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs b/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs index b9f1d360..71677c05 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs @@ -42,6 +42,18 @@ public static IApplicationBuilder UseMiddleware(this IApplicationBu /// The instance. public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) { + if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) + { + // IMiddleware doesn't support passing args directly since it's + // activated from the container + if (args.Length > 0) + { + throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware))); + } + + return UseMiddlewareInterface(app, middleware); + } + var applicationServices = app.ApplicationServices; return app.Use(next => { @@ -93,6 +105,38 @@ public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Ty }); } + private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType) + { + return app.Use(next => + { + return async context => + { + var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory)); + if (middlewareFactory == null) + { + // No middleware factory + throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory))); + } + + var middleware = middlewareFactory.Create(middlewareType); + if (middleware == null) + { + // The factory returned null, it's a broken implementation + throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType)); + } + + try + { + await middleware.Invoke(context, next); + } + finally + { + middlewareFactory.Release(middleware); + } + }; + }); + } + private static Func Compile(MethodInfo methodinfo, ParameterInfo[] parameters) { // If we call something like diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/IMiddleware.cs b/src/Microsoft.AspNetCore.Http.Abstractions/IMiddleware.cs new file mode 100644 index 00000000..53d54983 --- /dev/null +++ b/src/Microsoft.AspNetCore.Http.Abstractions/IMiddleware.cs @@ -0,0 +1,25 @@ +// 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 System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Http +{ + /// + /// Defines middleware that can be added to the application's request pipeline. + /// + public interface IMiddleware + { + /// + /// Request handling method. + /// + /// The for the current request. + /// The delegate representing the remaining middleware in the request pipeline. + /// A that represents the execution of this middleware. + Task Invoke(HttpContext context, RequestDelegate next); + } +} diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/IMiddlewareFactory.cs b/src/Microsoft.AspNetCore.Http.Abstractions/IMiddlewareFactory.cs new file mode 100644 index 00000000..be5af344 --- /dev/null +++ b/src/Microsoft.AspNetCore.Http.Abstractions/IMiddlewareFactory.cs @@ -0,0 +1,30 @@ +// 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 System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Http +{ + /// + /// Provides methods to create middlware. + /// + public interface IMiddlewareFactory + { + /// + /// Creates a middleware instance for each request. + /// + /// The concrete of the . + /// The instance. + IMiddleware Create(Type middlewareType); + + /// + /// Releases a instance at the end of each request. + /// + /// The instance to release. + void Release(IMiddleware middleware); + } +} diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Http.Abstractions/Properties/Resources.Designer.cs index 40e42556..c91b54e3 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/Properties/Resources.Designer.cs @@ -154,6 +154,54 @@ internal static string FormatException_PortMustBeGreaterThanZero() return GetString("Exception_PortMustBeGreaterThanZero"); } + /// + /// No service for type '{0}' has been registered. + /// + internal static string Exception_UseMiddlewareNoMiddlewareFactory + { + get { return GetString("Exception_UseMiddlewareNoMiddlewareFactory"); } + } + + /// + /// No service for type '{0}' has been registered. + /// + internal static string FormatException_UseMiddlewareNoMiddlewareFactory(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoMiddlewareFactory"), p0); + } + + /// + /// '{0}' failed to create middleware of type '{1}'. + /// + internal static string Exception_UseMiddlewareUnableToCreateMiddleware + { + get { return GetString("Exception_UseMiddlewareUnableToCreateMiddleware"); } + } + + /// + /// '{0}' failed to create middleware of type '{1}'. + /// + internal static string FormatException_UseMiddlewareUnableToCreateMiddleware(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareUnableToCreateMiddleware"), p0, p1); + } + + /// + /// Types that implement '{0}' do not support explicit arguments. + /// + internal static string Exception_UseMiddlewareExplicitArgumentsNotSupported + { + get { return GetString("Exception_UseMiddlewareExplicitArgumentsNotSupported"); } + } + + /// + /// Types that implement '{0}' do not support explicit arguments. + /// + internal static string FormatException_UseMiddlewareExplicitArgumentsNotSupported(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareExplicitArgumentsNotSupported"), p0); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/Resources.resx b/src/Microsoft.AspNetCore.Http.Abstractions/Resources.resx index 19ce173d..b37e46a1 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/Resources.resx +++ b/src/Microsoft.AspNetCore.Http.Abstractions/Resources.resx @@ -144,4 +144,13 @@ The value must be greater than zero. + + No service for type '{0}' has been registered. + + + '{0}' failed to create middleware of type '{1}'. + + + Types that implement '{0}' do not support explicit arguments. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Http/MiddlewareFactory.cs b/src/Microsoft.AspNetCore.Http/MiddlewareFactory.cs new file mode 100644 index 00000000..5e5cd285 --- /dev/null +++ b/src/Microsoft.AspNetCore.Http/MiddlewareFactory.cs @@ -0,0 +1,35 @@ +// 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 System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Http +{ + public class MiddlewareFactory : IMiddlewareFactory + { + // The default middleware factory is just an IServiceProvider proxy. + // This should be registered as a scoped service so that the middleware instances + // don't end up being singletons. + private readonly IServiceProvider _serviceProvider; + + public MiddlewareFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public IMiddleware Create(Type middlewareType) + { + return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware; + } + + public void Release(IMiddleware middleware) + { + // The container owns the lifetime of the service + } + } +} diff --git a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/UseMiddlewareTest.cs b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/UseMiddlewareTest.cs index ce197e0c..8f0446ce 100644 --- a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/UseMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/UseMiddlewareTest.cs @@ -2,6 +2,7 @@ // 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.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder.Internal; @@ -83,14 +84,130 @@ public void UseMiddlewareWithIvokeWithOutAndRefThrows() var exception = Assert.Throws(() => builder.Build()); } + [Fact] + public void UseMiddlewareWithIMiddlewareThrowsIfParametersSpecified() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + var exception = Assert.Throws(() => builder.UseMiddleware(typeof(Middleware), "arg")); + Assert.Equal(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)), exception.Message); + } + + [Fact] + public async Task UseMiddlewareWithIMiddlewareThrowsIfNoIMiddlewareFactoryRegistered() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(Middleware)); + var app = builder.Build(); + var exception = await Assert.ThrowsAsync(async () => + { + var context = new DefaultHttpContext(); + var sp = new DummyServiceProvider(); + context.RequestServices = sp; + await app(context); + }); + Assert.Equal(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)), exception.Message); + } + + [Fact] + public async Task UseMiddlewareWithIMiddlewareThrowsIfMiddlewareFactoryCreateReturnsNull() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(Middleware)); + var app = builder.Build(); + var exception = await Assert.ThrowsAsync(async () => + { + var context = new DefaultHttpContext(); + var sp = new DummyServiceProvider(); + sp.AddService(typeof(IMiddlewareFactory), new BadMiddlewareFactory()); + context.RequestServices = sp; + await app(context); + }); + + Assert.Equal(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(typeof(BadMiddlewareFactory), typeof(Middleware)), exception.Message); + } + + [Fact] + public async Task UseMiddlewareWithIMiddlewareWorks() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(Middleware)); + var app = builder.Build(); + var context = new DefaultHttpContext(); + var sp = new DummyServiceProvider(); + var middlewareFactory = new BasicMiddlewareFactory(); + sp.AddService(typeof(IMiddlewareFactory), middlewareFactory); + context.RequestServices = sp; + await app(context); + Assert.Equal(true, context.Items["before"]); + Assert.Equal(true, context.Items["after"]); + Assert.NotNull(middlewareFactory.Created); + Assert.NotNull(middlewareFactory.Released); + Assert.IsType(typeof(Middleware), middlewareFactory.Created); + Assert.IsType(typeof(Middleware), middlewareFactory.Released); + Assert.Same(middlewareFactory.Created, middlewareFactory.Released); + } + + public class Middleware : IMiddleware + { + public async Task Invoke(HttpContext context, RequestDelegate next) + { + context.Items["before"] = true; + await next(context); + context.Items["after"] = true; + } + } + + public class BasicMiddlewareFactory : IMiddlewareFactory + { + public IMiddleware Created { get; private set; } + public IMiddleware Released { get; private set; } + + public IMiddleware Create(Type middlewareType) + { + Created = Activator.CreateInstance(middlewareType) as IMiddleware; + return Created; + } + + public void Release(IMiddleware middleware) + { + Released = middleware; + } + } + + public class BadMiddlewareFactory : IMiddlewareFactory + { + public IMiddleware Create(Type middlewareType) + { + return null; + } + + public void Release(IMiddleware middleware) + { + + } + } + private class DummyServiceProvider : IServiceProvider { + private Dictionary _services = new Dictionary(); + + public void AddService(Type type, object value) => _services[type] = value; + public object GetService(Type serviceType) { if (serviceType == typeof(IServiceProvider)) { return this; } + + if (_services.TryGetValue(serviceType, out object value)) + { + return value; + } return null; } }