-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Expand file tree
/
Copy pathRequestDelegateFactory.cs
More file actions
2952 lines (2560 loc) · 149 KB
/
RequestDelegateFactory.cs
File metadata and controls
2952 lines (2560 loc) · 149 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO.Pipelines;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Endpoints.FormMapping;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Http;
/// <summary>
/// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
/// </summary>
[RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")]
[RequiresDynamicCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")]
public static partial class RequestDelegateFactory
{
private static readonly MethodInfo ExecuteTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteTaskOfTFastMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfTFast), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskOfTFastMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfTFast), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteTaskResultOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteValueResultTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ExecuteAwaitedReturnMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteAwaitedReturn), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo GetHeaderSplitMethod = typeof(ParsingHelpers).GetMethod(nameof(ParsingHelpers.GetHeaderSplit), BindingFlags.Public | BindingFlags.Static, [typeof(IHeaderDictionary), typeof(string)])!;
private static readonly MethodInfo GetRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
private static readonly MethodInfo GetServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
private static readonly MethodInfo GetRequiredKeyedServiceMethod = typeof(ServiceProviderKeyedServiceExtensions).GetMethod(nameof(ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider), typeof(object) })!;
private static readonly MethodInfo GetKeyedServiceMethod = typeof(ServiceProviderKeyedServiceExtensions).GetMethod(nameof(ServiceProviderKeyedServiceExtensions.GetKeyedService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider), typeof(object) })!;
private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteResultWriteResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteWriteStringResponseAsync), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo StringIsNullOrEmptyMethod = typeof(string).GetMethod(nameof(string.IsNullOrEmpty), BindingFlags.Static | BindingFlags.Public)!;
private static readonly MethodInfo WrapObjectAsValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WrapObjectAsValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo TaskOfTToValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(TaskOfTToValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ValueTaskOfTToValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ValueTaskOfTToValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo ArrayEmptyOfObjectMethod = typeof(Array).GetMethod(nameof(Array.Empty), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(new Type[] { typeof(object) });
private static readonly PropertyInfo QueryIndexerProperty = typeof(IQueryCollection).GetProperty("Item")!;
private static readonly PropertyInfo RouteValuesIndexerProperty = typeof(RouteValueDictionary).GetProperty("Item")!;
private static readonly PropertyInfo HeaderIndexerProperty = typeof(IHeaderDictionary).GetProperty("Item")!;
private static readonly PropertyInfo FormFilesIndexerProperty = typeof(IFormFileCollection).GetProperty("Item")!;
private static readonly PropertyInfo FormIndexerProperty = typeof(IFormCollection).GetProperty("Item")!;
private static readonly MethodInfo JsonResultWriteResponseOfTFastAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WriteJsonResponseFast), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo JsonResultWriteResponseOfTAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WriteJsonResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, sourceValue, shouldThrow) =>
Log.ParameterBindingFailed(httpContext, parameterType, parameterName, sourceValue, shouldThrow));
private static readonly MethodInfo LogRequiredParameterNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, source, shouldThrow) =>
Log.RequiredParameterNotProvided(httpContext, parameterType, parameterName, source, shouldThrow));
private static readonly MethodInfo LogImplicitBodyNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, bool>>((httpContext, parameterName, shouldThrow) =>
Log.ImplicitBodyNotProvided(httpContext, parameterName, shouldThrow));
private static readonly MethodInfo LogFormMappingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, FormDataMappingException, bool>>((httpContext, parameterName, parameterType, exception, shouldThrow) =>
Log.FormDataMappingFailed(httpContext, parameterName, parameterType, exception, shouldThrow));
private static readonly ParameterExpression TargetExpr = Expression.Parameter(typeof(object), "target");
private static readonly ParameterExpression BodyValueExpr = Expression.Parameter(typeof(object), "bodyValue");
private static readonly ParameterExpression WasParamCheckFailureExpr = Expression.Variable(typeof(bool), "wasParamCheckFailure");
private static readonly ParameterExpression BoundValuesArrayExpr = Expression.Parameter(typeof(object[]), "boundValues");
private static readonly ParameterExpression HttpContextExpr = ParameterBindingMethodCache.SharedExpressions.HttpContextExpr;
private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestServices))!);
private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Request))!);
private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestAborted))!);
private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.User))!);
private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.RouteValues))!);
private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Query))!);
private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Headers))!);
private static readonly MemberExpression FormExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Form))!);
private static readonly MemberExpression RequestStreamExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Body))!);
private static readonly MemberExpression RequestPipeReaderExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.BodyReader))!);
private static readonly MemberExpression FormFilesExpr = Expression.Property(FormExpr, typeof(IFormCollection).GetProperty(nameof(IFormCollection.Files))!);
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(EmptyHttpResult) })!, Expression.Property(null, typeof(EmptyHttpResult), nameof(EmptyHttpResult.Instance)));
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.SharedExpressions.TempSourceStringExpr;
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression.Not(Expression.Call(StringIsNullOrEmptyMethod, TempSourceStringExpr));
private static readonly ConstructorInfo DefaultEndpointFilterInvocationContextConstructor = typeof(DefaultEndpointFilterInvocationContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!;
private static readonly MethodInfo EndpointFilterInvocationContextGetArgument = typeof(EndpointFilterInvocationContext).GetMethod(nameof(EndpointFilterInvocationContext.GetArgument))!;
private static readonly PropertyInfo ListIndexer = typeof(IList<object>).GetProperty("Item")!;
private static readonly ParameterExpression FilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "context");
private static readonly MemberExpression FilterContextHttpContextExpr = Expression.Property(FilterContextExpr, typeof(EndpointFilterInvocationContext).GetProperty(nameof(EndpointFilterInvocationContext.HttpContext))!);
private static readonly MemberExpression FilterContextArgumentsExpr = Expression.Property(FilterContextExpr, typeof(EndpointFilterInvocationContext).GetProperty(nameof(EndpointFilterInvocationContext.Arguments))!);
private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression.Property(FilterContextHttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "filterContext");
private static readonly ConstructorInfo FormDataReaderConstructor = typeof(FormDataReader).GetConstructor(new[] { typeof(IReadOnlyDictionary<FormKey, StringValues>), typeof(CultureInfo), typeof(Memory<char>), typeof(IFormFileCollection) })!;
private static readonly MethodInfo ProcessFormMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ProcessForm), BindingFlags.Static | BindingFlags.NonPublic)!;
private static readonly MethodInfo FormDataMapperMapMethod = typeof(FormDataMapper).GetMethod(nameof(FormDataMapper.Map))!;
private static readonly MethodInfo AsMemoryMethod = new Func<char[]?, int, int, Memory<char>>(MemoryExtensions.AsMemory).Method;
private static readonly MethodInfo ArrayPoolSharedReturnMethod = typeof(ArrayPool<char>).GetMethod(nameof(ArrayPool<char>.Shared.Return))!;
private static readonly string[] DefaultAcceptsAndProducesContentType = new[] { ContentTypeConstants.JsonContentType };
private static readonly string[] FormFileContentType = new[] { "multipart/form-data" };
private static readonly string[] FormContentType = new[] { "multipart/form-data", "application/x-www-form-urlencoded" };
private static readonly string[] PlaintextContentType = new[] { "text/plain" };
private static readonly Type[] StringTypes = new[] {typeof(string), typeof(StringValues), typeof(StringValues?) };
/// <summary>
/// Returns metadata inferred automatically for the <see cref="RequestDelegate"/> created by <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.
/// This includes metadata inferred by <see cref="IEndpointMetadataProvider"/> and <see cref="IEndpointParameterMetadataProvider"/> implemented by parameter and return types to the <paramref name="methodInfo"/>.
/// </summary>
/// <param name="methodInfo">The <see cref="MethodInfo"/> for the route handler to be passed to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</param>
/// <param name="options">The options that will be used when calling <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</param>
/// <returns>The <see cref="RequestDelegateMetadataResult"/> to be passed to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</returns>
public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null)
{
var factoryContext = CreateFactoryContext(options);
factoryContext.ArgumentExpressions = CreateArgumentsAndInferMetadata(methodInfo, factoryContext);
return new RequestDelegateMetadataResult
{
EndpointMetadata = AsReadOnlyList(factoryContext.EndpointBuilder.Metadata),
CachedFactoryContext = factoryContext,
};
}
/// <summary>
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
/// </summary>
/// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
/// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
/// <returns>The <see cref="RequestDelegateResult"/>.</returns>
public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options)
{
return Create(handler, options, metadataResult: null);
}
/// <summary>
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
/// </summary>
/// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
/// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
/// <param name="metadataResult">
/// The result returned from <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> if that was used to inferring metadata before creating the final RequestDelegate.
/// If <see langword="null"/>, this call to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/> method will infer the metadata that
/// <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> would have inferred for the same <see cref="Delegate.Method"/> and populate <see cref="RequestDelegateFactoryOptions.EndpointBuilder"/>
/// with that metadata. Otherwise, this metadata inference will be skipped as this step has already been done.
/// </param>
/// <returns>The <see cref="RequestDelegateResult"/>.</returns>
[SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null)
{
ArgumentNullException.ThrowIfNull(handler);
var targetExpression = handler.Target switch
{
object => Expression.Convert(TargetExpr, handler.Target.GetType()),
null => null,
};
var factoryContext = CreateFactoryContext(options, metadataResult, handler);
Expression<Func<HttpContext, object?>> targetFactory = (httpContext) => handler.Target;
var targetableRequestDelegate = CreateTargetableRequestDelegate(handler.Method, targetExpression, factoryContext, targetFactory);
RequestDelegate finalRequestDelegate = targetableRequestDelegate switch
{
// handler is a RequestDelegate that has not been modified by a filter. Short-circuit and return the original RequestDelegate back.
// It's possible a filter factory has still modified the endpoint metadata though.
null => (RequestDelegate)handler,
_ => httpContext => targetableRequestDelegate(handler.Target, httpContext),
};
return CreateRequestDelegateResult(finalRequestDelegate, factoryContext.EndpointBuilder);
}
/// <summary>
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
/// </summary>
/// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
/// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
/// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
/// <returns>The <see cref="RequestDelegate"/>.</returns>
public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory, RequestDelegateFactoryOptions? options)
{
return Create(methodInfo, targetFactory, options, metadataResult: null);
}
/// <summary>
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
/// </summary>
/// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
/// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
/// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
/// <param name="metadataResult">
/// The result returned from <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> if that was used to inferring metadata before creating the final RequestDelegate.
/// If <see langword="null"/>, this call to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/> method will infer the metadata that
/// <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> would have inferred for the same <see cref="Delegate.Method"/> and populate <see cref="RequestDelegateFactoryOptions.EndpointBuilder"/>
/// with that metadata. Otherwise, this metadata inference will be skipped as this step has already been done.
/// </param>
/// <returns>The <see cref="RequestDelegate"/>.</returns>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null)
{
ArgumentNullException.ThrowIfNull(methodInfo);
if (methodInfo.DeclaringType is null)
{
throw new ArgumentException($"{nameof(methodInfo)} does not have a declaring type.");
}
var factoryContext = CreateFactoryContext(options, metadataResult);
RequestDelegate finalRequestDelegate;
if (methodInfo.IsStatic)
{
var untargetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression: null, factoryContext);
// CreateTargetableRequestDelegate can only return null given a RequestDelegate passed into the other RDF.Create() overload.
Debug.Assert(untargetableRequestDelegate is not null);
finalRequestDelegate = httpContext => untargetableRequestDelegate(null, httpContext);
}
else
{
targetFactory ??= context => Activator.CreateInstance(methodInfo.DeclaringType)!;
var targetExpression = Expression.Convert(TargetExpr, methodInfo.DeclaringType);
var targetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression, factoryContext, context => targetFactory(context));
// CreateTargetableRequestDelegate can only return null given a RequestDelegate passed into the other RDF.Create() overload.
Debug.Assert(targetableRequestDelegate is not null);
finalRequestDelegate = httpContext => targetableRequestDelegate(targetFactory(httpContext), httpContext);
}
return CreateRequestDelegateResult(finalRequestDelegate, factoryContext.EndpointBuilder);
}
private static RequestDelegateFactoryContext CreateFactoryContext(RequestDelegateFactoryOptions? options, RequestDelegateMetadataResult? metadataResult = null, Delegate? handler = null)
{
if (metadataResult?.CachedFactoryContext is RequestDelegateFactoryContext cachedFactoryContext)
{
cachedFactoryContext.MetadataAlreadyInferred = true;
// The handler was not passed in to the InferMetadata call that originally created this context.
cachedFactoryContext.Handler = handler;
return cachedFactoryContext;
}
var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices ?? EmptyServiceProvider.Instance;
var endpointBuilder = options?.EndpointBuilder ?? new RdfEndpointBuilder(serviceProvider);
var jsonSerializerOptions = serviceProvider.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
var formDataMapperOptions = new FormDataMapperOptions();
var factoryContext = new RequestDelegateFactoryContext
{
Handler = handler,
ServiceProvider = serviceProvider,
ServiceProviderIsService = serviceProvider.GetService<IServiceProviderIsService>(),
RouteParameters = options?.RouteParameterNames,
ThrowOnBadRequest = options?.ThrowOnBadRequest ?? false,
DisableInferredFromBody = options?.DisableInferBodyFromParameters ?? false,
EndpointBuilder = endpointBuilder,
MetadataAlreadyInferred = metadataResult is not null,
JsonSerializerOptions = jsonSerializerOptions,
FormDataMapperOptions = formDataMapperOptions
};
return factoryContext;
}
private static RequestDelegateResult CreateRequestDelegateResult(RequestDelegate finalRequestDelegate, EndpointBuilder endpointBuilder)
{
endpointBuilder.RequestDelegate = finalRequestDelegate;
return new RequestDelegateResult(finalRequestDelegate, AsReadOnlyList(endpointBuilder.Metadata));
}
private static IReadOnlyList<object> AsReadOnlyList(IList<object> metadata)
{
if (metadata is IReadOnlyList<object> readOnlyList)
{
return readOnlyList;
}
return new List<object>(metadata);
}
private static Func<object?, HttpContext, Task>? CreateTargetableRequestDelegate(
MethodInfo methodInfo,
Expression? targetExpression,
RequestDelegateFactoryContext factoryContext,
Expression<Func<HttpContext, object?>>? targetFactory = null)
{
// Non void return type
// Task Invoke(HttpContext httpContext)
// {
// // Action parameters are bound from the request, services, etc... based on attribute and type information.
// return ExecuteTask(handler(...), httpContext);
// }
// void return type
// Task Invoke(HttpContext httpContext)
// {
// handler(...);
// return default;
// }
// If ArgumentExpressions is not null here, it's guaranteed we have already inferred metadata and we can reuse a lot of work.
// The converse is not true. Metadata may have already been inferred even if ArgumentExpressions is null, but metadata
// inference is skipped internally if necessary.
factoryContext.ArgumentExpressions ??= CreateArgumentsAndInferMetadata(methodInfo, factoryContext);
// Although we can re-use the cached argument expressions for most cases, parameters that are bound
// using the new form mapping logic are a special exception because we need to account for the `FormOptionsMetadata`
// added to the builder _during_ the construction of the parameter binding.
UpdateFormBindingArgumentExpressions(factoryContext);
factoryContext.MethodCall = CreateMethodCall(methodInfo, targetExpression, factoryContext.ArgumentExpressions);
EndpointFilterDelegate? filterPipeline = null;
var returnType = methodInfo.ReturnType;
// If there are filters registered on the route handler, then we update the method call and
// return type associated with the request to allow for the filter invocation pipeline.
if (factoryContext.EndpointBuilder.FilterFactories.Count > 0)
{
filterPipeline = CreateFilterPipeline(methodInfo, targetExpression, factoryContext, targetFactory);
if (filterPipeline is not null)
{
Expression<Func<EndpointFilterInvocationContext, ValueTask<object?>>> invokePipeline = (context) => filterPipeline(context);
returnType = typeof(ValueTask<object?>);
// var filterContext = new EndpointFilterInvocationContext<string, int>(httpContext, name_local, int_local);
// invokePipeline.Invoke(filterContext);
factoryContext.MethodCall = Expression.Block(
new[] { InvokedFilterContextExpr },
Expression.Assign(
InvokedFilterContextExpr,
CreateEndpointFilterInvocationContextBase(factoryContext, factoryContext.ArgumentExpressions)),
Expression.Invoke(invokePipeline, InvokedFilterContextExpr)
);
}
}
// return null for plain RequestDelegates that have not been modified by filters so we can just pass back the original RequestDelegate.
if (filterPipeline is null && factoryContext.Handler is RequestDelegate)
{
return null;
}
var responseWritingMethodCall = factoryContext.ParamCheckExpressions.Count > 0 ?
CreateParamCheckingResponseWritingMethodCall(returnType, factoryContext) :
AddResponseWritingToMethodCall(factoryContext.MethodCall, returnType, factoryContext);
if (factoryContext.UsingTempSourceString)
{
responseWritingMethodCall = Expression.Block(new[] { TempSourceStringExpr }, responseWritingMethodCall);
}
return HandleRequestBodyAndCompileRequestDelegate(responseWritingMethodCall, factoryContext);
}
private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInfo, RequestDelegateFactoryContext factoryContext)
{
// Add any default accepts metadata. This does a lot of reflection and expression tree building, so the results are cached in RequestDelegateFactoryOptions.FactoryContext
// For later reuse in Create().
var args = CreateArguments(methodInfo.GetParameters(), factoryContext);
if (!factoryContext.MetadataAlreadyInferred)
{
if (factoryContext.ReadForm)
{
// Add the Accepts metadata when reading from FORM.
InferFormAcceptsMetadata(factoryContext);
InferAntiforgeryMetadata(factoryContext);
}
// If this endpoint expects a JSON request body, we assume its an API endpoint not intended for browser navigation.
// When present, authentication handlers should prefer returning status codes over browser redirects.
if (factoryContext.JsonRequestBodyParameter is not null)
{
factoryContext.EndpointBuilder.Metadata.Add(DisableCookieRedirectMetadata.Instance);
}
PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext);
// Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
EndpointMetadataPopulator.PopulateMetadata(methodInfo, factoryContext.EndpointBuilder, factoryContext.Parameters);
}
return args;
}
private static EndpointFilterDelegate? CreateFilterPipeline(MethodInfo methodInfo, Expression? targetExpression, RequestDelegateFactoryContext factoryContext, Expression<Func<HttpContext, object?>>? targetFactory)
{
Debug.Assert(factoryContext.EndpointBuilder.FilterFactories.Count > 0);
// httpContext.Response.StatusCode >= 400
// ? Task.CompletedTask
// : {
// handlerInvocation
// }
// To generate the handler invocation, we first create the
// target of the handler provided to the route.
// target = targetFactory(httpContext);
// This target is then used to generate the handler invocation like so;
// ((Type)target).MethodName(parameters);
// When `handler` returns an object, we generate the following wrapper
// to convert it to `ValueTask<object?>` as expected in the filter
// pipeline.
// ValueTask<object?>.FromResult(handler(EndpointFilterInvocationContext.GetArgument<string>(0), EndpointFilterInvocationContext.GetArgument<int>(1)));
// When the `handler` is a generic Task or ValueTask we await the task and
// create a `ValueTask<object?> from the resulting value.
// new ValueTask<object?>(await handler(EndpointFilterInvocationContext.GetArgument<string>(0), EndpointFilterInvocationContext.GetArgument<int>(1)));
// When the `handler` returns a void or a void-returning Task, then we return an EmptyHttpResult
// to as a ValueTask<object?>
// }
var argTypes = factoryContext.ArgumentTypes;
var contextArgAccess = new Expression[argTypes.Length];
for (var i = 0; i < argTypes.Length; i++)
{
// MakeGenericMethod + value type requires IsDynamicCodeSupported to be true.
if (RuntimeFeature.IsDynamicCodeSupported)
{
// Register expressions containing the boxed and unboxed variants
// of the route handler's arguments for use in EndpointFilterInvocationContext
// construction and route handler invocation.
// context.GetArgument<string>(0)
// (string, name_local), (int, int_local)
contextArgAccess[i] = Expression.Call(FilterContextExpr, EndpointFilterInvocationContextGetArgument.MakeGenericMethod(argTypes[i]), Expression.Constant(i));
}
else
{
// We box if dynamic code isn't supported
contextArgAccess[i] = Expression.Convert(
Expression.Property(FilterContextArgumentsExpr, ListIndexer, Expression.Constant(i)),
argTypes[i]);
}
}
var handlerReturnMapping = MapHandlerReturnTypeToValueTask(
targetExpression is null
? Expression.Call(methodInfo, contextArgAccess)
: Expression.Call(targetExpression, methodInfo, contextArgAccess),
methodInfo.ReturnType);
var handlerInvocation = Expression.Block(
new[] { TargetExpr },
targetFactory == null
? Expression.Empty()
: Expression.Assign(TargetExpr, Expression.Invoke(targetFactory, FilterContextHttpContextExpr)),
handlerReturnMapping
);
var filteredInvocation = Expression.Lambda<EndpointFilterDelegate>(
Expression.Condition(
Expression.GreaterThanOrEqual(FilterContextHttpContextStatusCodeExpr, Expression.Constant(400)),
EmptyHttpResultValueTaskExpr,
handlerInvocation),
FilterContextExpr).Compile();
var routeHandlerContext = new EndpointFilterFactoryContext
{
MethodInfo = methodInfo,
ApplicationServices = factoryContext.EndpointBuilder.ApplicationServices,
};
var initialFilteredInvocation = filteredInvocation;
for (var i = factoryContext.EndpointBuilder.FilterFactories.Count - 1; i >= 0; i--)
{
var currentFilterFactory = factoryContext.EndpointBuilder.FilterFactories[i];
filteredInvocation = currentFilterFactory(routeHandlerContext, filteredInvocation);
}
// The filter factories have run without modifying per-request behavior, we can skip running the pipeline.
// If a plain old RequestDelegate was passed in (with no generic parameter), we can just return it back directly now.
if (ReferenceEquals(initialFilteredInvocation, filteredInvocation))
{
return null;
}
return filteredInvocation;
}
private static Expression MapHandlerReturnTypeToValueTask(Expression methodCall, Type returnType)
{
if (returnType == typeof(void))
{
return Expression.Block(methodCall, EmptyHttpResultValueTaskExpr);
}
else if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
{
if (coercedAwaitableInfo.CoercerResultType is { } coercedType)
{
returnType = coercedType;
}
if (coercedAwaitableInfo.CoercerExpression is { } coercerExpression)
{
methodCall = Expression.Invoke(coercerExpression, methodCall);
}
if (returnType == typeof(Task))
{
return Expression.Call(ExecuteTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask))
{
return Expression.Call(ExecuteValueTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask<object?>))
{
return methodCall;
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
return Expression.Call(ValueTaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
return Expression.Call(TaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
}
if (returnType.IsValueType)
{
return Expression.Call(WrapObjectAsValueTaskMethod, Expression.Convert(methodCall, typeof(object)));
}
return Expression.Call(WrapObjectAsValueTaskMethod, methodCall);
}
private static ValueTask<object?> ValueTaskOfTToValueTaskOfObject<T>(ValueTask<T> valueTask)
{
static async ValueTask<object?> ExecuteAwaited(ValueTask<T> valueTask)
{
return await valueTask;
}
if (valueTask.IsCompletedSuccessfully)
{
return new ValueTask<object?>(valueTask.Result);
}
return ExecuteAwaited(valueTask);
}
private static ValueTask<object?> TaskOfTToValueTaskOfObject<T>(Task<T> task)
{
static async ValueTask<object?> ExecuteAwaited(Task<T> task)
{
return await task;
}
if (task.IsCompletedSuccessfully)
{
return new ValueTask<object?>(task.Result);
}
return ExecuteAwaited(task);
}
private static Expression CreateEndpointFilterInvocationContextBase(RequestDelegateFactoryContext factoryContext, Expression[] arguments)
{
// In the event that a constructor matching the arity of the
// provided parameters is not found, we fall back to using the
// non-generic implementation of EndpointFilterInvocationContext.
Expression paramArray = factoryContext.BoxedArgs.Length > 0
? Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs)
: Expression.Call(ArrayEmptyOfObjectMethod);
var fallbackConstruction = Expression.New(
DefaultEndpointFilterInvocationContextConstructor,
new Expression[] { HttpContextExpr, paramArray });
if (!RuntimeFeature.IsDynamicCodeSupported)
{
// For AOT platforms it's not possible to support the closed generic arguments that are based on the
// parameter arguments dynamically (for value types). In that case, fallback to boxing the argument list.
return fallbackConstruction;
}
var expandedArguments = new Expression[arguments.Length + 1];
expandedArguments[0] = HttpContextExpr;
arguments.CopyTo(expandedArguments, 1);
var constructorType = factoryContext.ArgumentTypes?.Length switch
{
1 => typeof(EndpointFilterInvocationContext<>),
2 => typeof(EndpointFilterInvocationContext<,>),
3 => typeof(EndpointFilterInvocationContext<,,>),
4 => typeof(EndpointFilterInvocationContext<,,,>),
5 => typeof(EndpointFilterInvocationContext<,,,,>),
6 => typeof(EndpointFilterInvocationContext<,,,,,>),
7 => typeof(EndpointFilterInvocationContext<,,,,,,>),
8 => typeof(EndpointFilterInvocationContext<,,,,,,,>),
9 => typeof(EndpointFilterInvocationContext<,,,,,,,,>),
10 => typeof(EndpointFilterInvocationContext<,,,,,,,,,>),
_ => typeof(DefaultEndpointFilterInvocationContext)
};
if (constructorType.IsGenericType)
{
var constructor = constructorType.MakeGenericType(factoryContext.ArgumentTypes!).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).SingleOrDefault();
if (constructor == null)
{
// new EndpointFilterInvocationContext(httpContext, (object)name_local, (object)int_local);
return fallbackConstruction;
}
// new EndpointFilterInvocationContext<string, int>(httpContext, name_local, int_local);
return Expression.New(constructor, expandedArguments);
}
// new EndpointFilterInvocationContext(httpContext, (object)name_local, (object)int_local);
return fallbackConstruction;
}
private static Expression[] CreateArguments(ParameterInfo[]? parameters, RequestDelegateFactoryContext factoryContext)
{
if (parameters is null || parameters.Length == 0)
{
return Array.Empty<Expression>();
}
var args = new Expression[parameters.Length];
factoryContext.ArgumentTypes = new Type[parameters.Length];
factoryContext.BoxedArgs = new Expression[parameters.Length];
factoryContext.Parameters = new List<ParameterInfo>(parameters);
for (var i = 0; i < parameters.Length; i++)
{
args[i] = CreateArgument(parameters[i], factoryContext, out var hasTryParse, out var hasBindAsync, out var isAsParameters);
if (!isAsParameters)
{
factoryContext.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata(
name: parameters[i].Name!,
parameterInfo: parameters[i],
hasTryParse: hasTryParse,
hasBindAsync: hasBindAsync,
isOptional: IsOptionalParameter(parameters[i], factoryContext)
));
}
factoryContext.ArgumentTypes[i] = parameters[i].ParameterType;
factoryContext.BoxedArgs[i] = Expression.Convert(args[i], typeof(object));
}
if (factoryContext.HasInferredBody && factoryContext.DisableInferredFromBody)
{
var errorMessage = BuildErrorMessageForInferredBodyParameter(factoryContext);
throw new InvalidOperationException(errorMessage);
}
if (factoryContext.JsonRequestBodyParameter is not null &&
factoryContext.FirstFormRequestBodyParameter is not null)
{
var errorMessage = BuildErrorMessageForFormAndJsonBodyParameters(factoryContext);
throw new InvalidOperationException(errorMessage);
}
if (factoryContext.HasMultipleBodyParameters)
{
var errorMessage = BuildErrorMessageForMultipleBodyParameters(factoryContext);
throw new InvalidOperationException(errorMessage);
}
return args;
}
private static Expression CreateArgument(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext, out bool hasTryParse, out bool hasBindAsync, out bool isAsParameters)
{
hasTryParse = false;
hasBindAsync = false;
isAsParameters = false;
if (parameter.Name is null)
{
throw new InvalidOperationException($"Encountered a parameter of type '{parameter.ParameterType}' without a name. Parameters must have a name.");
}
if (parameter.ParameterType.IsByRef)
{
var attribute = "ref";
if (parameter.Attributes.HasFlag(ParameterAttributes.In))
{
attribute = "in";
}
else if (parameter.Attributes.HasFlag(ParameterAttributes.Out))
{
attribute = "out";
}
throw new NotSupportedException($"The by reference parameter '{attribute} {TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)} {parameter.Name}' is not supported.");
}
var parameterCustomAttributes = parameter.GetCustomAttributes();
if (parameterCustomAttributes.OfType<IFromRouteMetadata>().FirstOrDefault() is { } routeAttribute)
{
var routeName = routeAttribute.Name ?? parameter.Name;
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteAttribute);
if (factoryContext.RouteParameters is { } routeParams && !routeParams.Contains(routeName, StringComparer.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"'{routeName}' is not a route parameter.");
}
return BindParameterFromProperty(parameter, RouteValuesExpr, RouteValuesIndexerProperty, routeName, factoryContext, "route");
}
else if (parameterCustomAttributes.OfType<IFromQueryMetadata>().FirstOrDefault() is { } queryAttribute)
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryAttribute);
return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, queryAttribute.Name ?? parameter.Name, factoryContext, "query string");
}
else if (parameterCustomAttributes.OfType<IFromHeaderMetadata>().FirstOrDefault() is { } headerAttribute)
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.HeaderAttribute);
return BindParameterFromProperty(parameter, HeadersExpr, HeaderIndexerProperty, headerAttribute.Name ?? parameter.Name, factoryContext, "header");
}
else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyAttribute);
if (parameter.ParameterType == typeof(Stream))
{
return RequestStreamExpr;
}
else if (parameter.ParameterType == typeof(PipeReader))
{
return RequestPipeReaderExpr;
}
return BindParameterFromBody(parameter, bodyAttribute.AllowEmpty, factoryContext);
}
else if (parameterCustomAttributes.OfType<IFromFormMetadata>().FirstOrDefault() is { } formAttribute)
{
if (parameter.ParameterType == typeof(IFormFileCollection))
{
if (!string.IsNullOrEmpty(formAttribute.Name))
{
throw new NotSupportedException(
$"Assigning a value to the {nameof(IFromFormMetadata)}.{nameof(IFromFormMetadata.Name)} property is not supported for parameters of type {nameof(IFormFileCollection)}.");
}
return BindParameterFromFormFiles(parameter, factoryContext);
}
else if (parameter.ParameterType == typeof(IFormFile))
{
return BindParameterFromFormFile(parameter, formAttribute.Name ?? parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileAttribute);
}
else if (parameter.ParameterType == typeof(IFormCollection))
{
if (!string.IsNullOrEmpty(formAttribute.Name))
{
throw new NotSupportedException(
$"Assigning a value to the {nameof(IFromFormMetadata)}.{nameof(IFromFormMetadata.Name)} property is not supported for parameters of type {nameof(IFormCollection)}.");
}
return BindParameterFromFormCollection(parameter, factoryContext);
}
// Continue to use the simple binding support that exists in RDF/RDG for currently
// supported scenarios to maintain compatible semantics between versions of RDG.
// For complex types, leverage the shared form binding infrastructure. For example,
// shared form binding does not currently only supports types that implement IParsable
// while RDF's binding implementation supports all TryParse implementations.
var useSimpleBinding = StringTypes.Contains(parameter.ParameterType) ||
ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType) ||
(parameter.ParameterType.IsArray &&
(StringTypes.Contains(parameter.ParameterType.GetElementType()) ||
ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType.GetElementType()!)));
hasTryParse = useSimpleBinding;
return useSimpleBinding
? BindParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext)
: BindComplexParameterFromFormItem(parameter, string.IsNullOrEmpty(formAttribute.Name) ? parameter.Name : formAttribute.Name, factoryContext);
}
else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
{
if (parameterCustomAttributes.FirstOrDefault(a => typeof(FromKeyedServicesAttribute).IsAssignableFrom(a.GetType())) is not null)
{
throw new NotSupportedException(
$"The {nameof(FromKeyedServicesAttribute)} is not supported on parameters that are also annotated with {nameof(IFromServiceMetadata)}.");
}
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceAttribute);
return BindParameterFromService(parameter, factoryContext);
}
else if (parameterCustomAttributes.FirstOrDefault(a => typeof(FromKeyedServicesAttribute).IsAssignableFrom(a.GetType())) is FromKeyedServicesAttribute keyedServicesAttribute)
{
if (factoryContext.ServiceProviderIsService is not IServiceProviderIsKeyedService)
{
throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services.");
}
var key = keyedServicesAttribute.Key;
return BindParameterFromKeyedService(parameter, key, factoryContext);
}
else if (parameterCustomAttributes.OfType<AsParametersAttribute>().Any())
{
isAsParameters = true;
if (parameter is PropertyAsParameterInfo)
{
throw new NotSupportedException(
$"Nested {nameof(AsParametersAttribute)} is not supported and should be used only for handler parameters.");
}
return BindParameterFromProperties(parameter, factoryContext);
}
else if (parameter.ParameterType == typeof(HttpContext))
{
return HttpContextExpr;
}
else if (parameter.ParameterType == typeof(HttpRequest))
{
return HttpRequestExpr;
}
else if (parameter.ParameterType == typeof(HttpResponse))
{
return HttpResponseExpr;
}
else if (parameter.ParameterType == typeof(ClaimsPrincipal))
{
return UserExpr;
}
else if (parameter.ParameterType == typeof(CancellationToken))
{
return RequestAbortedExpr;
}
else if (parameter.ParameterType == typeof(IFormCollection))
{
return BindParameterFromFormCollection(parameter, factoryContext);
}
else if (parameter.ParameterType == typeof(IFormFileCollection))
{
return BindParameterFromFormFiles(parameter, factoryContext);
}
else if (parameter.ParameterType == typeof(IFormFile))
{
return BindParameterFromFormFile(parameter, parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileParameter);
}
else if (parameter.ParameterType == typeof(Stream))
{
return RequestStreamExpr;
}
else if (parameter.ParameterType == typeof(PipeReader))
{
return RequestPipeReaderExpr;
}
else if (ParameterBindingMethodCache.Instance.HasBindAsyncMethod(parameter))
{
hasBindAsync = true;
return BindParameterFromBindAsync(parameter, factoryContext);
}
else if (parameter.ParameterType == typeof(string) || ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType))
{
hasTryParse = true;
// 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
// 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
// 3. Otherwise, we fallback to route or query if route parameters is null (it means we don't know what route parameters are defined). This case only happens
// when RDF.Create is manually invoked.
if (factoryContext.RouteParameters is { } routeParams)
{
if (routeParams.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase))
{
// We're in the fallback case and we have a parameter and route parameter match so don't fallback
// to query string in this case
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteParameter);
return BindParameterFromProperty(parameter, RouteValuesExpr, RouteValuesIndexerProperty, parameter.Name, factoryContext, "route");
}
else
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, parameter.Name, factoryContext, "query string");
}
}
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteOrQueryStringParameter);
return BindParameterFromRouteValueOrQueryString(parameter, parameter.Name, factoryContext);
}
else if (factoryContext.DisableInferredFromBody && (
parameter.ParameterType == typeof(string[]) ||
parameter.ParameterType == typeof(StringValues) ||
parameter.ParameterType == typeof(StringValues?) ||
(parameter.ParameterType.IsArray && ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType.GetElementType()!))))
{
// We only infer parameter types if you have an array of TryParsables/string[]/StringValues/StringValues?, and DisableInferredFromBody is true
hasTryParse = true;
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, parameter.Name, factoryContext, "query string");
}
else
{
if (factoryContext.ServiceProviderIsService is IServiceProviderIsService serviceProviderIsService)
{
if (serviceProviderIsService.IsService(parameter.ParameterType))
{
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceParameter);
return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
}
}
factoryContext.HasInferredBody = true;
factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyParameter);
return BindParameterFromBody(parameter, allowEmpty: false, factoryContext);
}
}
private static Expression CreateMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments) =>
target is null ?
Expression.Call(methodInfo, arguments) :
Expression.Call(target, methodInfo, arguments);
private static ValueTask<object?> WrapObjectAsValueTask(object? obj)
{
return ValueTask.FromResult<object?>(obj);
}
// If we're calling TryParse or validating parameter optionality and
// wasParamCheckFailure indicates it failed, set a 400 StatusCode instead of calling the method.
private static Expression CreateParamCheckingResponseWritingMethodCall(Type returnType, RequestDelegateFactoryContext factoryContext)
{
// {
// string tempSourceString;
// bool wasParamCheckFailure = false;
//
// // Assume "int param1" is the first parameter, "[FromRoute] int? param2 = 42" is the second parameter ...
// int param1_local;
// int? param2_local;
// // ...
//
// tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
//
// if (tempSourceString != null)
// {
// if (!int.TryParse(tempSourceString, out param1_local))
// {
// wasParamCheckFailure = true;
// Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
// }
// }
//
// tempSourceString = httpContext.RouteValue["param2"];
// // ...
//
// return wasParamCheckFailure ?
// {
// httpContext.Response.StatusCode = 400;
// return Task.CompletedTask;
// } :
// {
// // Logic generated by AddResponseWritingToMethodCall() that calls handler(param1_local, param2_local, ...)
// };
// }
var localVariables = new ParameterExpression[factoryContext.ExtraLocals.Count + 1];
var checkParamAndCallMethod = new Expression[factoryContext.ParamCheckExpressions.Count + 1];
for (var i = 0; i < factoryContext.ExtraLocals.Count; i++)
{
localVariables[i] = factoryContext.ExtraLocals[i];
}
for (var i = 0; i < factoryContext.ParamCheckExpressions.Count; i++)
{
checkParamAndCallMethod[i] = factoryContext.ParamCheckExpressions[i];
}
localVariables[factoryContext.ExtraLocals.Count] = WasParamCheckFailureExpr;
// If filters have been registered, we set the `wasParamCheckFailure` property
// but do not return from the invocation to allow the filters to run.
if (factoryContext.EndpointBuilder.FilterFactories.Count > 0)
{
// if (wasParamCheckFailure)