9
9
using System . Linq . Expressions ;
10
10
using System . Reflection ;
11
11
using Microsoft . AspNetCore . Http ;
12
+ using Microsoft . AspNetCore . Http . Metadata ;
12
13
using Microsoft . AspNetCore . Mvc . Abstractions ;
13
14
using Microsoft . AspNetCore . Mvc . ModelBinding . Metadata ;
14
15
using Microsoft . AspNetCore . Mvc . ModelBinding . Validation ;
@@ -30,6 +31,17 @@ public abstract class ModelMetadata : IEquatable<ModelMetadata?>, IModelMetadata
30
31
private static readonly ParameterBindingMethodCache ParameterBindingMethodCache
31
32
= new ( throwOnInvalidMethod : false ) ;
32
33
34
+ /// <summary>
35
+ /// Exposes a feature switch to disable generating model metadata with reflection-heavy strategies.
36
+ /// This is primarily intended for use in Minimal API-based scenarios where information is derived from
37
+ /// IParameterBindingMetadata
38
+ /// </summary>
39
+ [ FeatureSwitchDefinition ( "Microsoft.AspNetCore.Mvc.ApiExplorer.IsEnhancedModelMetadataSupported" ) ]
40
+ [ FeatureGuard ( typeof ( RequiresDynamicCodeAttribute ) ) ]
41
+ [ FeatureGuard ( typeof ( RequiresUnreferencedCodeAttribute ) ) ]
42
+ private static bool IsEnhancedModelMetadataSupported { get ; } =
43
+ AppContext . TryGetSwitch ( "Microsoft.AspNetCore.Mvc.ApiExplorer.IsEnhancedModelMetadataSupported" , out var isEnhancedModelMetadataSupported ) ? isEnhancedModelMetadataSupported : true ;
44
+
33
45
private int ? _hashCode ;
34
46
private IReadOnlyList < ModelMetadata > ? _boundProperties ;
35
47
private IReadOnlyDictionary < ModelMetadata , ModelMetadata > ? _parameterMapping ;
@@ -44,8 +56,58 @@ private static readonly ParameterBindingMethodCache ParameterBindingMethodCache
44
56
protected ModelMetadata ( ModelMetadataIdentity identity )
45
57
{
46
58
Identity = identity ;
59
+ if ( IsEnhancedModelMetadataSupported )
60
+ {
61
+ InitializeTypeInformation ( ) ;
62
+ }
63
+ }
64
+
65
+ /// <summary>
66
+ /// Creates a new <see cref="ModelMetadata"/> from a <see cref="IParameterBindingMetadata"/> instance
67
+ /// and its associated type.
68
+ /// </summary>
69
+ /// <param name="type">The <see cref="Type"/> associated with the <see cref="ModelMetadata"/> generated.</param>
70
+ /// <param name="parameterBindingMetadata">The <see cref="IParameterBindingMetadata"/> instance associated with the <see cref="ModelMetadata"/> generated.</param>
71
+ protected ModelMetadata ( Type type , IParameterBindingMetadata ? parameterBindingMetadata )
72
+ {
73
+ Identity = ModelMetadataIdentity . ForType ( type ) ;
47
74
48
- InitializeTypeInformation ( ) ;
75
+ InitializeTypeInformationFromType ( ) ;
76
+ if ( parameterBindingMetadata is not null )
77
+ {
78
+ InitializeTypeInformationFromParameterBindingMetadata ( parameterBindingMetadata ) ;
79
+ }
80
+ }
81
+
82
+ private void InitializeTypeInformationFromType ( )
83
+ {
84
+ IsNullableValueType = Nullable . GetUnderlyingType ( ModelType ) != null ;
85
+ IsReferenceOrNullableType = ! ModelType . IsValueType || IsNullableValueType ;
86
+ UnderlyingOrModelType = Nullable . GetUnderlyingType ( ModelType ) ?? ModelType ;
87
+
88
+ if ( ModelType == typeof ( string ) || ! typeof ( IEnumerable ) . IsAssignableFrom ( ModelType ) )
89
+ {
90
+ // Do nothing, not Enumerable.
91
+ }
92
+ else if ( ModelType . IsArray )
93
+ {
94
+ IsEnumerableType = true ;
95
+ ElementType = ModelType . GetElementType ( ) ! ;
96
+ }
97
+ }
98
+
99
+ private void InitializeTypeInformationFromParameterBindingMetadata ( IParameterBindingMetadata parameterBindingMetadata )
100
+ {
101
+ // We assume that parameters bound from an endpoint's metadata originated from minimal API's source
102
+ // generation layer and are not convertible based on the `TypeConverter`s in MVC.
103
+ IsConvertibleType = false ;
104
+ HasDefaultValue = parameterBindingMetadata . ParameterInfo . HasDefaultValue ;
105
+ IsParseableType = parameterBindingMetadata . HasTryParse ;
106
+ IsComplexType = ! IsParseableType ;
107
+
108
+ var nullabilityContext = new NullabilityInfoContext ( ) ;
109
+ var nullability = nullabilityContext . Create ( parameterBindingMetadata . ParameterInfo ) ;
110
+ NullabilityState = nullability ? . ReadState ?? NullabilityState . Unknown ;
49
111
}
50
112
51
113
/// <summary>
@@ -442,7 +504,7 @@ internal IReadOnlyDictionary<ModelMetadata, ModelMetadata> BoundConstructorPrope
442
504
/// from <see cref="string"/> and without a <c>TryParse</c> method. Most POCO and <see cref="IEnumerable"/> types are therefore complex.
443
505
/// Most, if not all, BCL value types are simple types.
444
506
/// </remarks>
445
- public bool IsComplexType => ! IsConvertibleType && ! IsParseableType ;
507
+ public bool IsComplexType { get ; private set ; }
446
508
447
509
/// <summary>
448
510
/// Gets a value indicating whether or not <see cref="ModelType"/> is a <see cref="Nullable{T}"/>.
@@ -647,12 +709,13 @@ public override int GetHashCode()
647
709
return _hashCode . Value ;
648
710
}
649
711
712
+ [ RequiresUnreferencedCode ( "Using ModelMetadata with 'Microsoft.AspNetCore.Mvc.ApiExplorer.IsEnhancedModelMetadataSupported=true' is not trim compatible." ) ]
713
+ [ RequiresDynamicCode ( "Using ModelMetadata with 'Microsoft.AspNetCore.Mvc.ApiExplorer.IsEnhancedModelMetadataSupported=true' is not native AOT compatible." ) ]
650
714
private void InitializeTypeInformation ( )
651
715
{
652
- Debug . Assert ( ModelType != null ) ;
653
-
654
716
IsConvertibleType = TypeDescriptor . GetConverter ( ModelType ) . CanConvertFrom ( typeof ( string ) ) ;
655
717
IsParseableType = FindTryParseMethod ( ModelType ) is not null ;
718
+ IsComplexType = ! IsConvertibleType && ! IsParseableType ;
656
719
IsNullableValueType = Nullable . GetUnderlyingType ( ModelType ) != null ;
657
720
IsReferenceOrNullableType = ! ModelType . IsValueType || IsNullableValueType ;
658
721
UnderlyingOrModelType = Nullable . GetUnderlyingType ( ModelType ) ?? ModelType ;
0 commit comments