diff --git a/src/Microsoft.AspNet.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs b/src/Microsoft.AspNet.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs index b247ab3cac..04430164df 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/Extensions/UseMiddlewareExtensions.cs @@ -6,12 +6,14 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Abstractions; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Builder { public static class UseMiddlewareExtensions { + const string InvokeMethodName = "Invoke"; public static IApplicationBuilder UseMiddleware(this IApplicationBuilder builder, params object[] args) { return builder.UseMiddleware(typeof(T), args); @@ -22,24 +24,44 @@ namespace Microsoft.AspNet.Builder var applicationServices = builder.ApplicationServices; return builder.Use(next => { - var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, new[] { next }.Concat(args).ToArray()); - var methodinfo = middleware.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); - var parameters = methodinfo.GetParameters(); - if (parameters[0].ParameterType != typeof(HttpContext)) + var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); + var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray(); + if (invokeMethods.Length > 1) { - throw new Exception("Middleware Invoke method must take first argument of HttpContext"); + throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName)); } + + if (invokeMethods.Length == 0) + { + throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName)); + } + + var methodinfo = invokeMethods[0]; + if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType)) + { + throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, nameof(Task))); + } + + var parameters = methodinfo.GetParameters(); + if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) + { + throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,nameof(HttpContext))); + } + + var instance = ActivatorUtilities.CreateInstance(builder.ApplicationServices, middleware, new[] { next }.Concat(args).ToArray()); if (parameters.Length == 1) { return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance); } + return context => { var serviceProvider = context.RequestServices ?? context.ApplicationServices ?? applicationServices; if (serviceProvider == null) { - throw new Exception("IServiceProvider is not available"); + 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) diff --git a/src/Microsoft.AspNet.Http.Abstractions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Http.Abstractions/Properties/AssemblyInfo.cs index 025a94598c..2565f9261e 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/Properties/AssemblyInfo.cs @@ -2,5 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Reflection; +using System.Runtime.CompilerServices; -[assembly: AssemblyMetadata("Serviceable", "True")] \ No newline at end of file +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: InternalsVisibleTo("Microsoft.AspNet.Http.Abstractions.Tests")] \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Http.Abstractions/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..9fb86a30c6 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Abstractions/Properties/Resources.Designer.cs @@ -0,0 +1,110 @@ +// +namespace Microsoft.AspNet.Http.Abstractions +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNet.Http.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// '{0}' is not available. + /// + internal static string Exception_UseMiddlewareIServiceProviderNotAvailable + { + get { return GetString("Exception_UseMiddlewareIServiceProviderNotAvailable"); } + } + + /// + /// '{0}' is not available. + /// + internal static string FormatException_UseMiddlewareIServiceProviderNotAvailable(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareIServiceProviderNotAvailable"), p0); + } + + /// + /// No public '{0}' method found. + /// + internal static string Exception_UseMiddlewareNoInvokeMethod + { + get { return GetString("Exception_UseMiddlewareNoInvokeMethod"); } + } + + /// + /// No public '{0}' method found. + /// + internal static string FormatException_UseMiddlewareNoInvokeMethod(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoInvokeMethod"), p0); + } + + /// + /// '{0}' does not return an object of type '{1}'. + /// + internal static string Exception_UseMiddlewareNonTaskReturnType + { + get { return GetString("Exception_UseMiddlewareNonTaskReturnType"); } + } + + /// + /// '{0}' does not return an object of type '{1}'. + /// + internal static string FormatException_UseMiddlewareNonTaskReturnType(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNonTaskReturnType"), p0, p1); + } + + /// + /// The '{0}' method's first argument must be of type '{1}'. + /// + internal static string Exception_UseMiddlewareNoParameters + { + get { return GetString("Exception_UseMiddlewareNoParameters"); } + } + + /// + /// The '{0}' method's first argument must be of type '{1}'. + /// + internal static string FormatException_UseMiddlewareNoParameters(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoParameters"), p0, p1); + } + + /// + /// Multiple public '{0}' methods are available. + /// + internal static string Exception_UseMiddleMutlipleInvokes + { + get { return GetString("Exception_UseMiddleMutlipleInvokes"); } + } + + /// + /// Multiple public '{0}' methods are available. + /// + internal static string FormatException_UseMiddleMutlipleInvokes(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNet.Http.Abstractions/Resources.resx b/src/Microsoft.AspNet.Http.Abstractions/Resources.resx new file mode 100644 index 0000000000..bdd74fd6de --- /dev/null +++ b/src/Microsoft.AspNet.Http.Abstractions/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{0}' is not available. + + + No public '{0}' method found. + + + '{0}' does not return an object of type '{1}'. + + + The '{0}' method's first argument must be of type '{1}'. + + + Multiple public '{0}' methods are available. + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Http.Abstractions.Tests/UseMiddlewareTest.cs b/test/Microsoft.AspNet.Http.Abstractions.Tests/UseMiddlewareTest.cs new file mode 100644 index 0000000000..1839631d61 --- /dev/null +++ b/test/Microsoft.AspNet.Http.Abstractions.Tests/UseMiddlewareTest.cs @@ -0,0 +1,112 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Builder.Internal; +using Microsoft.AspNet.Http.Abstractions; +using Xunit; + +namespace Microsoft.AspNet.Http +{ + public class UseMiddlewareTest + { + [Fact] + public void UseMiddleware_WithNoParameters_ThrowsException() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(MiddlewareNoParametersStub)); + var exception = Assert.Throws(() => builder.Build()); + + Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke",nameof(HttpContext)), exception.Message); + } + + [Fact] + public void UseMiddleware_NonTaskReturnType_ThrowsException() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(MiddlewareNonTaskReturnStub)); + var exception = Assert.Throws(() => builder.Build()); + Assert.Equal(Resources.FormatException_UseMiddlewareNonTaskReturnType("Invoke", nameof(Task)), exception.Message); + } + + [Fact] + public void UseMiddleware_NoInvokeMethod_ThrowsException() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(MiddlewareNoInvokeStub)); + var exception = Assert.Throws(() => builder.Build()); + Assert.Equal(Resources.FormatException_UseMiddlewareNoInvokeMethod("Invoke"), exception.Message); + } + + [Fact] + public void UseMiddleware_MutlipleInvokeMethods_ThrowsException() + { + var mockServiceProvider = new DummyServiceProvider(); + var builder = new ApplicationBuilder(mockServiceProvider); + builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub)); + var exception = Assert.Throws(() => builder.Build()); + Assert.Equal(Resources.FormatException_UseMiddleMutlipleInvokes("Invoke"), exception.Message); + } + + private class DummyServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) + { + return null; + } + } + + private class MiddlewareNoParametersStub + { + public MiddlewareNoParametersStub(RequestDelegate next) + { + } + + public Task Invoke() + { + return Task.FromResult(0); + } + } + + private class MiddlewareNonTaskReturnStub + { + public MiddlewareNonTaskReturnStub(RequestDelegate next) + { + } + + public int Invoke() + { + return 0; + } + } + + private class MiddlewareNoInvokeStub + { + public MiddlewareNoInvokeStub(RequestDelegate next) + { + } + } + + private class MiddlewareMultipleInvokesStub + { + public MiddlewareMultipleInvokesStub(RequestDelegate next) + { + } + + public Task Invoke(HttpContext context) + { + return Task.FromResult(0); + } + + public Task Invoke(HttpContext context, int i) + { + return Task.FromResult(0); + } + } + } +} \ No newline at end of file