11using System . Reflection ;
2- using AuthKit . Plugins . Abstractions ;
32using AuthKit . Plugins . Abstractions . Contracts ;
3+ using AuthKit . Plugins . Abstractions . Contracts . PluginContract ;
4+ using AuthKit . Plugins . Abstractions . Pipeline ;
45using Host . Plugins . Loading ;
56using IAuthKitPlugin = AuthKit . Plugins . Abstractions . Contracts . PluginContract . IAuthKitPlugin ;
67
@@ -11,15 +12,15 @@ namespace Host.Plugins.Configuration;
1112/// </summary>
1213/// <remarks>
1314/// <para>
14- /// Plugins are invoked in a stable order regardless of the order they were
15+ /// Plugins are invoked in stable order regardless of the order they were
1516/// discovered: first by <see cref="PluginPipelinePosition"/> and then by
1617/// plugin identifier using an ordinal comparison.
1718/// </para>
1819/// <para>
19- /// Plugins that do not implement a given hook are skipped. The newer
20+ /// Plugins that do not implement given hook are skipped. The newer
2021/// <c>ConfigureApplication</c> and <c>ConfigurePipeline</c> hooks take
2122/// precedence over the legacy <c>MiddlewareType</c> entry point, which is
22- /// applied only as a compatibility fallback.
23+ /// applied only as compatibility fallback.
2324/// </para>
2425/// </remarks>
2526internal static class PluginApplicationConfiguration
@@ -56,12 +57,12 @@ public static void ConfigureApplications(IApplicationBuilder application, IReadO
5657 /// <param name="plugins">The plugins loaded during application startup.</param>
5758 /// <param name="position">The pipeline position to run hooks for.</param>
5859 /// <exception cref="ArgumentOutOfRangeException">
59- /// Thrown when <paramref name="position"/> is not a defined
60+ /// Thrown when <paramref name="position"/> is not defined
6061 /// <see cref="PluginPipelinePosition"/> value.
6162 /// </exception>
6263 /// <exception cref="InvalidOperationException">
63- /// Thrown when a plugin that implements the pipeline hook declares a
64- /// <c>PipelinePosition</c> that is not a defined enum value.
64+ /// Thrown when plugin that implements the pipeline hook declares a
65+ /// <c>PipelinePosition</c> that is not defined enum value.
6566 /// </exception>
6667 public static void ConfigurePipeline (
6768 IApplicationBuilder application ,
@@ -134,6 +135,92 @@ public static void ConfigureLegacyMiddleware(WebApplication application, IReadOn
134135 }
135136 }
136137
138+ /// <summary>
139+ /// Inserts declarative <see cref="PluginMiddleware"/> entries for one
140+ /// <see cref="PipelinePosition"/> in deterministic order
141+ /// (Order -> stable plugin Id -> declaration index).
142+ /// Disabled entries are skipped without side effects.
143+ /// <see cref="IAuthKitMiddleware"/> and <see cref="AuthKitMiddlewareBase"/>
144+ /// implementations are resolved from the request
145+ /// service provider (single request scope); other types use
146+ /// <c>UseMiddleware</c> activation.
147+ /// </summary>
148+ public static void ConfigurePluginMiddlewares (
149+ IApplicationBuilder application ,
150+ IReadOnlyList < LoadedPlugin > plugins ,
151+ PipelinePosition position )
152+ {
153+ if ( ! Enum . IsDefined ( position ) )
154+ throw new ArgumentOutOfRangeException ( nameof ( position ) , position , "Unsupported pipeline position." ) ;
155+
156+ var ordered = plugins
157+ . SelectMany ( lp => ( lp . Plugin . Middlewares ?? [ ] )
158+ . Select ( ( mw , index ) => ( Plugin : lp . Plugin , Entry : mw , Index : index ) ) )
159+ . Where ( x => x . Entry . Position == position && x . Entry . IsMiddlewareEnabled
160+ && x . Entry . Transport == AuthKitTransport . Http )
161+ . OrderBy ( x => x . Entry . Order )
162+ . ThenBy ( x => x . Plugin . Id , StringComparer . Ordinal )
163+ . ThenBy ( x => x . Index )
164+ . ToList ( ) ;
165+
166+ foreach ( var ( plugin , entry , _) in ordered )
167+ {
168+ if ( entry . MiddlewareType is null )
169+ throw new InvalidOperationException ( $ "Plugin '{ plugin . Id } ' declares middleware with null type.") ;
170+
171+ try
172+ {
173+ RegisterPluginMiddleware ( application , entry . MiddlewareType ) ;
174+ }
175+ catch ( Exception ex )
176+ {
177+ throw new InvalidOperationException ( $ "Plugin '{ plugin . Id } ' failed to register middleware '{ entry . MiddlewareType ? . Name ?? entry . Name } '.", ex ) ;
178+ }
179+ }
180+ }
181+
182+ private static void RegisterPluginMiddleware ( IApplicationBuilder application , Type middlewareType ) =>
183+ application . UseWhen (
184+ static context => ! IsGrpcRequest ( context ) ,
185+ branch => RegisterHttpMiddleware ( branch , middlewareType ) ) ;
186+
187+ private static bool IsGrpcRequest ( HttpContext context ) =>
188+ context . Request . ContentType ? . StartsWith ( "application/grpc" , StringComparison . OrdinalIgnoreCase ) == true ;
189+
190+ private static void RegisterHttpMiddleware ( IApplicationBuilder application , Type middlewareType )
191+ {
192+ if ( typeof ( IAuthKitMiddleware ) . IsAssignableFrom ( middlewareType ) )
193+ {
194+ application . Use ( async ( context , next ) =>
195+ {
196+ var middleware = ResolvePluginMiddleware < IAuthKitMiddleware > ( context , middlewareType ) ;
197+ await middleware . InvokeAsync ( context , next ) ;
198+ } ) ;
199+
200+ return ;
201+ }
202+
203+ if ( typeof ( AuthKitMiddlewareBase ) . IsAssignableFrom ( middlewareType ) )
204+ {
205+ application . Use ( async ( context , next ) =>
206+ {
207+ var middleware = ResolvePluginMiddleware < AuthKitMiddlewareBase > ( context , middlewareType ) ;
208+ await middleware . InvokeAsync ( context , next ) ;
209+ } ) ;
210+
211+ return ;
212+ }
213+
214+ application . UseMiddleware ( middlewareType ) ;
215+ }
216+
217+ private static TMiddleware ResolvePluginMiddleware < TMiddleware > ( HttpContext context , Type middlewareType )
218+ where TMiddleware : class =>
219+ context . RequestServices . GetService ( middlewareType ) as TMiddleware
220+ ?? ActivatorUtilities . CreateInstance ( context . RequestServices , middlewareType ) as TMiddleware
221+ ?? throw new InvalidOperationException (
222+ $ "Middleware type '{ middlewareType . FullName } ' must be assignable to '{ typeof ( TMiddleware ) . FullName } '.") ;
223+
137224 /// <summary>
138225 /// Orders plugins by pipeline position and then by plugin identifier.
139226 /// </summary>
@@ -165,14 +252,14 @@ private static void ValidatePluginPositions(IReadOnlyList<LoadedPlugin> plugins)
165252 }
166253
167254 /// <summary>
168- /// Determines whether a plugin provides a concrete implementation of the given
255+ /// Determines whether plugin provides concrete implementation of the given
169256 /// hook rather than inheriting the interface's default implementation.
170257 /// </summary>
171258 /// <param name="plugin">The plugin to inspect.</param>
172259 /// <param name="methodName">The name of the interface method to look up.</param>
173260 /// <param name="parameterTypes">The parameter types that identify the overload.</param>
174261 /// <returns>
175- /// <c>true</c> when the plugin overrides the hook; otherwise, <c>false</c>.
262+ /// <c>true</c> when the plugin overrides the hook otherwise, <c>false</c>.
176263 /// </returns>
177264 private static bool HasImplementation ( IAuthKitPlugin plugin , string methodName , params Type [ ] parameterTypes )
178265 {
@@ -185,4 +272,4 @@ private static bool HasImplementation(IAuthKitPlugin plugin, string methodName,
185272
186273 return method is not null && method . DeclaringType != typeof ( IAuthKitPlugin ) ;
187274 }
188- }
275+ }
0 commit comments