@@ -76,30 +76,20 @@ public static T GetRequiredRequestService<T>(this IApplicationBuilder builder)
7676 /// <param name="container">The container to resolve <typeparamref name="TMiddleware"/> from.</param>
7777 /// <returns>The supplied <see cref="IApplicationBuilder"/> instance.</returns>
7878 /// <exception cref="ArgumentNullException">Thrown when one of the arguments is a null reference.</exception>
79+ /// <exception cref="ArgumentException">Thrown when the <typeparamref name="TMiddleware"/> does not
80+ /// implement <see cref="IMiddleware"/> or not a concrete constructable
81+ /// type.</exception>
7982 public static IApplicationBuilder UseMiddleware < TMiddleware > (
8083 this IApplicationBuilder app , Container container )
81- where TMiddleware : class , IMiddleware
8284 {
83- Requires . IsNotNull ( app , nameof ( app ) ) ;
84- Requires . IsNotNull ( container , nameof ( container ) ) ;
85-
86- var lifestyle = container . Options . LifestyleSelectionBehavior . SelectLifestyle ( typeof ( TMiddleware ) ) ;
87-
88- // By creating an InstanceProducer up front, it will be known to the container, and will be part
89- // of the verification process of the container.
90- // Note that the middleware can't be registered in the container, because at this point the
91- // container might already be locked (which will happen when the new ASP.NET Core 3 Host class is
92- // used).
93- InstanceProducer < IMiddleware > producer =
94- lifestyle . CreateProducer < IMiddleware , TMiddleware > ( container ) ;
95-
96- app . Use ( ( c , next ) =>
97- {
98- IMiddleware middleware = producer . GetInstance ( ) ;
99- return middleware . InvokeAsync ( c , _ => next ( ) ) ;
100- } ) ;
101-
102- return app ;
85+ // DESIGN: The "where TMiddleware : class, IMiddleware" generic type constraint was deliberately removed
86+ // from this generic method. In case a user calls app.UserMiddleware<T>(container) on a T that didn't
87+ // implement IMiddleware, C# would call one of ASP.NET Core's built-in extension method (i.e.
88+ // UseMiddlewareExtensions.UseMiddleware<T>(IApplicationBuilder, object[])) instead of giving a compile
89+ // error. This would result in a really confusing error message. By removing the constraint, C# will still
90+ // prefer calling this method, in which case we can throw an expressive exception explaining that
91+ // TMiddleware should implement IMiddleware. See #5
92+ return UseMiddlewareInternal ( app , typeof ( TMiddleware ) , container , nameof ( TMiddleware ) ) ;
10393 }
10494
10595 /// <summary>
@@ -114,30 +104,40 @@ public static IApplicationBuilder UseMiddleware<TMiddleware>(
114104 /// <returns>The supplied <see cref="IApplicationBuilder"/> instance.</returns>
115105 /// <exception cref="ArgumentNullException">Thrown when one of the arguments is a null reference.</exception>
116106 /// <exception cref="ArgumentException">Thrown when the <paramref name="middlewareType"/> does not
117- /// derive from <see cref="IMiddleware"/>, is an open-generic type, or not a concrete constructable
118- /// type.</exception>
107+ /// implement <see cref="IMiddleware"/>, is open-generic, or not a concrete constructable type.</exception>
119108 public static IApplicationBuilder UseMiddleware (
120109 this IApplicationBuilder app , Type middlewareType , Container container )
110+ {
111+ return UseMiddlewareInternal ( app , middlewareType , container , nameof ( middlewareType ) ) ;
112+ }
113+
114+ private static IApplicationBuilder UseMiddlewareInternal (
115+ IApplicationBuilder app , Type middlewareType , Container container , string middlewareTypeArgName )
121116 {
122117 Requires . IsNotNull ( app , nameof ( app ) ) ;
123- Requires . IsNotNull ( middlewareType , nameof ( middlewareType ) ) ;
118+ Requires . IsNotNull ( middlewareType , middlewareTypeArgName ) ;
124119 Requires . IsNotNull ( container , nameof ( container ) ) ;
125120
126- Requires . ServiceIsAssignableFromImplementation (
127- typeof ( IMiddleware ) , middlewareType , nameof ( middlewareType ) ) ;
121+ Requires . ServiceIsAssignableFromImplementation ( typeof ( IMiddleware ) , middlewareType , middlewareTypeArgName ) ;
128122
129- Requires . IsNotOpenGenericType ( middlewareType , nameof ( middlewareType ) ) ;
123+ Requires . IsNotOpenGenericType ( middlewareType , middlewareTypeArgName ) ;
130124
131125 var lifestyle = container . Options . LifestyleSelectionBehavior . SelectLifestyle ( middlewareType ) ;
132126
133- // By creating an InstanceProducer up front, it will be known to the container, and will be part
134- // of the verification process of the container.
135- // Note that the middleware can't be registered in the container, because at this point the
136- // container might already be locked (which will happen when the new ASP.NET Core 3 Host class is
137- // used).
138- InstanceProducer < IMiddleware > producer =
139- lifestyle . CreateProducer < IMiddleware > ( middlewareType , container ) ;
140-
127+ // By creating an InstanceProducer up front, it will be known to the container, and will be part of the
128+ // verification process of the container (in case verification is done after a call to UseMiddleware).
129+ // NOTE: the middleware can't be registered in the container using container.Register, because:
130+ // 1. at this point the container might already be locked (which will happen when the new ASP.NET Core 3
131+ // Host class is used).
132+ // 2. This method might be used in a map, e.g. app.Map("/api/abc", b => b.UseMiddleware<M>(container)).
133+ // When there are multiple mappings to the same middleware type M, the second call would fail.
134+ InstanceProducer < IMiddleware > producer = lifestyle . CreateProducer < IMiddleware > ( middlewareType , container ) ;
135+
136+ // NOTE: CreateProducer will deduplicate the underlying Registration for middlewareType, which means its
137+ // lifestyle will be respected in case there are multiple maps to the same type. But in case decorators are
138+ // applied to IMiddleware, each map gets its own decorator (even if the decorator is registered as
139+ // singleton). This isn't per se the best design, but keep this in mind when changing this; users might be
140+ // depending on this behavior.
141141 app . Use ( ( c , next ) =>
142142 {
143143 IMiddleware middleware = producer . GetInstance ( ) ;
0 commit comments