@@ -47,6 +47,8 @@ public static partial class RequestDelegateFactory
47
47
private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteWriteStringResponseAsync ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
48
48
private static readonly MethodInfo StringIsNullOrEmptyMethod = typeof ( string ) . GetMethod ( nameof ( string . IsNullOrEmpty ) , BindingFlags . Static | BindingFlags . Public ) ! ;
49
49
private static readonly MethodInfo WrapObjectAsValueTaskMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( WrapObjectAsValueTask ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
50
+ private static readonly MethodInfo PopulateMetadataForParameterMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( PopulateMetadataForParameter ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
51
+ private static readonly MethodInfo PopulateMetadataForEndpointMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( PopulateMetadataForEndpoint ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
50
52
51
53
// Call WriteAsJsonAsync<object?>() to serialize the runtime return type rather than the declared return type.
52
54
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism
@@ -165,16 +167,26 @@ public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpConte
165
167
return new RequestDelegateResult ( httpContext => targetableRequestDelegate ( targetFactory ( httpContext ) , httpContext ) , factoryContext . Metadata ) ;
166
168
}
167
169
168
- private static FactoryContext CreateFactoryContext ( RequestDelegateFactoryOptions ? options ) =>
169
- new ( )
170
+ private static FactoryContext CreateFactoryContext ( RequestDelegateFactoryOptions ? options )
171
+ {
172
+ var context = new FactoryContext
170
173
{
174
+ ServiceProvider = options ? . ServiceProvider ,
171
175
ServiceProviderIsService = options ? . ServiceProvider ? . GetService < IServiceProviderIsService > ( ) ,
172
176
RouteParameters = options ? . RouteParameterNames ? . ToList ( ) ,
173
177
ThrowOnBadRequest = options ? . ThrowOnBadRequest ?? false ,
174
178
DisableInferredFromBody = options ? . DisableInferBodyFromParameters ?? false ,
175
179
Filters = options ? . RouteHandlerFilterFactories ? . ToList ( )
176
180
} ;
177
181
182
+ if ( options ? . InitialEndpointMetadata is not null )
183
+ {
184
+ context . Metadata . AddRange ( options . InitialEndpointMetadata ) ;
185
+ }
186
+
187
+ return context ;
188
+ }
189
+
178
190
private static Func < object ? , HttpContext , Task > CreateTargetableRequestDelegate ( MethodInfo methodInfo , Expression ? targetExpression , FactoryContext factoryContext )
179
191
{
180
192
// Non void return type
@@ -193,10 +205,20 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
193
205
// return default;
194
206
// }
195
207
208
+ // Add MethodInfo as first metadata item
209
+ factoryContext . Metadata . Insert ( 0 , methodInfo ) ;
210
+
211
+ // CreateArguments will add metadata inferred from parameter details
196
212
var arguments = CreateArguments ( methodInfo . GetParameters ( ) , factoryContext ) ;
197
213
var returnType = methodInfo . ReturnType ;
198
214
factoryContext . MethodCall = CreateMethodCall ( methodInfo , targetExpression , arguments ) ;
199
215
216
+ // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
217
+ AddTypeProvidedMetadata ( methodInfo , factoryContext . Metadata , factoryContext . ServiceProvider ) ;
218
+
219
+ // Add method attributes as metadata *after* any inferred metadata so that the attributes hava a higher specificity
220
+ AddMethodAttributesAsMetadata ( methodInfo , factoryContext . Metadata ) ;
221
+
200
222
// If there are filters registered on the route handler, then we update the method call and
201
223
// return type associated with the request to allow for the filter invocation pipeline.
202
224
if ( factoryContext . Filters is { Count : > 0 } )
@@ -261,6 +283,82 @@ target is null
261
283
return filteredInvocation ;
262
284
}
263
285
286
+ private static void AddTypeProvidedMetadata ( MethodInfo methodInfo , List < object > metadata , IServiceProvider ? services )
287
+ {
288
+ object ? [ ] ? invokeArgs = null ;
289
+
290
+ // Get metadata from parameter types
291
+ var parameters = methodInfo . GetParameters ( ) ;
292
+ foreach ( var parameter in parameters )
293
+ {
294
+ if ( typeof ( IEndpointParameterMetadataProvider ) . IsAssignableFrom ( parameter . ParameterType ) )
295
+ {
296
+ // Parameter type implements IEndpointParameterMetadataProvider
297
+ var parameterContext = new EndpointParameterMetadataContext
298
+ {
299
+ Parameter = parameter ,
300
+ EndpointMetadata = metadata ,
301
+ Services = services
302
+ } ;
303
+ invokeArgs ??= new object [ 1 ] ;
304
+ invokeArgs [ 0 ] = parameterContext ;
305
+ PopulateMetadataForParameterMethod . MakeGenericMethod ( parameter . ParameterType ) . Invoke ( null , invokeArgs ) ;
306
+ }
307
+
308
+ if ( typeof ( IEndpointMetadataProvider ) . IsAssignableFrom ( parameter . ParameterType ) )
309
+ {
310
+ // Parameter type implements IEndpointMetadataProvider
311
+ var context = new EndpointMetadataContext
312
+ {
313
+ Method = methodInfo ,
314
+ EndpointMetadata = metadata ,
315
+ Services = services
316
+ } ;
317
+ invokeArgs ??= new object [ 1 ] ;
318
+ invokeArgs [ 0 ] = context ;
319
+ PopulateMetadataForEndpointMethod . MakeGenericMethod ( parameter . ParameterType ) . Invoke ( null , invokeArgs ) ;
320
+ }
321
+ }
322
+
323
+ // Get metadata from return type
324
+ if ( methodInfo . ReturnType is not null && typeof ( IEndpointMetadataProvider ) . IsAssignableFrom ( methodInfo . ReturnType ) )
325
+ {
326
+ // Return type implements IEndpointMetadataProvider
327
+ var context = new EndpointMetadataContext
328
+ {
329
+ Method = methodInfo ,
330
+ EndpointMetadata = metadata ,
331
+ Services = services
332
+ } ;
333
+ invokeArgs ??= new object [ 1 ] ;
334
+ invokeArgs [ 0 ] = context ;
335
+ PopulateMetadataForEndpointMethod . MakeGenericMethod ( methodInfo . ReturnType ) . Invoke ( null , invokeArgs ) ;
336
+ }
337
+ }
338
+
339
+ private static void PopulateMetadataForParameter < T > ( EndpointParameterMetadataContext parameterContext )
340
+ where T : IEndpointParameterMetadataProvider
341
+ {
342
+ T . PopulateMetadata ( parameterContext ) ;
343
+ }
344
+
345
+ private static void PopulateMetadataForEndpoint < T > ( EndpointMetadataContext context )
346
+ where T : IEndpointMetadataProvider
347
+ {
348
+ T . PopulateMetadata ( context ) ;
349
+ }
350
+
351
+ private static void AddMethodAttributesAsMetadata ( MethodInfo methodInfo , List < object > metadata )
352
+ {
353
+ var attributes = methodInfo . GetCustomAttributes ( ) ;
354
+
355
+ // This can be null if the delegate is a dynamic method or compiled from an expression tree
356
+ if ( attributes is not null )
357
+ {
358
+ metadata . AddRange ( attributes ) ;
359
+ }
360
+ }
361
+
264
362
private static Expression [ ] CreateArguments ( ParameterInfo [ ] ? parameters , FactoryContext factoryContext )
265
363
{
266
364
if ( parameters is null || parameters . Length == 0 )
@@ -1679,6 +1777,7 @@ private static async Task ExecuteResultWriteResponse(IResult? result, HttpContex
1679
1777
private class FactoryContext
1680
1778
{
1681
1779
// Options
1780
+ public IServiceProvider ? ServiceProvider { get ; init ; }
1682
1781
public IServiceProviderIsService ? ServiceProviderIsService { get ; init ; }
1683
1782
public List < string > ? RouteParameters { get ; init ; }
1684
1783
public bool ThrowOnBadRequest { get ; init ; }
@@ -1697,7 +1796,7 @@ private class FactoryContext
1697
1796
public bool HasMultipleBodyParameters { get ; set ; }
1698
1797
public bool HasInferredBody { get ; set ; }
1699
1798
1700
- public List < object > Metadata { get ; } = new ( ) ;
1799
+ public List < object > Metadata { get ; internal set ; } = new ( ) ;
1701
1800
1702
1801
public NullabilityInfoContext NullabilityContext { get ; } = new ( ) ;
1703
1802
0 commit comments