1
- using Microsoft . AspNetCore . Mvc . ModelBinding ;
1
+ using Microsoft . AspNetCore . Mvc . ModelBinding ;
2
2
using Microsoft . AspNetCore . Mvc . ModelBinding . Binders ;
3
3
using Newtonsoft . Json ;
4
4
using Newtonsoft . Json . Linq ;
5
5
using System ;
6
+ using System . Collections . Generic ;
6
7
using System . IO ;
7
8
using System . Linq ;
8
9
using System . Reflection ;
@@ -32,15 +33,24 @@ public IModelBinder GetBinder(ModelBinderProviderContext context)
32
33
33
34
public class TupleModelBinder : IModelBinder
34
35
{
36
+ private static readonly Type [ ] VALUE_TUPLE_TYPES = new [ ] {
37
+ typeof ( ValueTuple < > ) ,
38
+ typeof ( ValueTuple < , > ) ,
39
+ typeof ( ValueTuple < , , > ) ,
40
+ typeof ( ValueTuple < , , , > ) ,
41
+ typeof ( ValueTuple < , , , , > ) ,
42
+ typeof ( ValueTuple < , , , , , > ) ,
43
+ typeof ( ValueTuple < , , , , , , > ) ,
44
+ typeof ( ValueTuple < , , , , , , , > ) ,
45
+ } ;
46
+
35
47
public async Task BindModelAsync ( ModelBindingContext bindingContext )
36
48
{
37
49
if ( bindingContext == null )
38
50
{
39
51
throw new ArgumentNullException ( nameof ( bindingContext ) ) ;
40
52
}
41
53
42
- //var modelName = bindingContext.ModelName;
43
-
44
54
var reader = new StreamReader ( bindingContext . HttpContext . Request . Body ) ;
45
55
46
56
var body = await reader . ReadToEndAsync ( ) ;
@@ -51,39 +61,49 @@ public async Task BindModelAsync(ModelBindingContext bindingContext)
51
61
if ( tupleAttr == null )
52
62
{
53
63
bindingContext . Result = ModelBindingResult . Failed ( ) ;
64
+ return ;
54
65
}
55
- else
56
- {
57
- var tupleType = bindingContext . ModelType ;
58
- object tuple = ParseTupleFromModelAttributes ( body , tupleAttr , tupleType ) ;
59
- bindingContext . Result = ModelBindingResult . Success ( tuple ) ;
60
- }
61
- }
62
66
63
- public static object ParseTupleFromModelAttributes ( string body , TupleElementNamesAttribute tupleAttr , Type tupleType )
64
- {
65
67
var jobj = JObject . Parse ( body ) ;
66
- var parameters = tupleAttr . TransformNames . Zip ( tupleType . GetConstructors ( )
67
- . Single ( )
68
- . GetParameters ( ) )
69
- . Select ( x => GetValue ( jobj , x . First , x . Second ) )
70
- . ToArray ( ) ;
68
+ int parameterIndex = 0 ;
69
+ object tuple = ParseTupleFromModelAttributes ( jobj , tupleAttr . TransformNames , bindingContext . ModelType , ref parameterIndex ) ;
71
70
72
- object tuple = Activator . CreateInstance ( tupleType , parameters ) ;
71
+ bindingContext . Result = ModelBindingResult . Success ( tuple ) ;
72
+ }
73
+
74
+ public static object ParseTupleFromModelAttributes ( JObject jobject , IList < string > parameterNames , Type parameterType , ref int parameterIndex )
75
+ {
76
+ if ( parameterType . GenericTypeArguments . Length > VALUE_TUPLE_TYPES . Length )
77
+ throw new InvalidOperationException ( $ "Cannot bind model of ValueTuple with more than { VALUE_TUPLE_TYPES . Length } generic type arguments.") ;
73
78
74
- return tuple ;
79
+ bool isNestedValueTuple = parameterType . GenericTypeArguments . Length == VALUE_TUPLE_TYPES . Length ;
80
+ int numSimpleGenericTypeArgs = Math . Min ( VALUE_TUPLE_TYPES . Length - 1 , parameterType . GenericTypeArguments . Length ) ;
81
+ object [ ] parameters = new object [ parameterType . GenericTypeArguments . Length ] ;
82
+ for ( int a = 0 ; a < numSimpleGenericTypeArgs ; ++ a )
83
+ {
84
+ parameters [ a ] = GetValue ( jobject , parameterNames [ parameterIndex ] , parameterType . GenericTypeArguments [ a ] ) ;
85
+ ++ parameterIndex ;
86
+ }
87
+ if ( isNestedValueTuple )
88
+ {
89
+ Type nestedValueTupleType = parameterType . GenericTypeArguments [ VALUE_TUPLE_TYPES . Length - 1 ] ;
90
+ parameters [ VALUE_TUPLE_TYPES . Length - 1 ] = ParseTupleFromModelAttributes ( jobject , parameterNames , nestedValueTupleType , ref parameterIndex ) ;
91
+ }
92
+ Type genericValueTupleType = VALUE_TUPLE_TYPES [ parameterType . GenericTypeArguments . Length - 1 ] ;
93
+ Type concreteValueTupleType = genericValueTupleType . MakeGenericType ( parameterType . GenericTypeArguments ) ;
94
+ return Activator . CreateInstance ( concreteValueTupleType , parameters ) ;
75
95
}
76
96
77
- static object GetValue ( JObject jobject , string name , ParameterInfo info )
97
+ static object GetValue ( JObject jobject , string name , Type parameterType )
78
98
{
79
99
var value = jobject . GetValue ( name , StringComparison . CurrentCultureIgnoreCase ) ;
80
100
81
101
if ( value == null || value . Type == JTokenType . Null )
82
102
{
83
- if ( IsNullable ( info . ParameterType ) )
84
- return Convert . ChangeType ( null , info . ParameterType ) ;
103
+ if ( IsNullable ( parameterType ) )
104
+ return Convert . ChangeType ( null , parameterType ) ;
85
105
else
86
- return Activator . CreateInstance ( info . ParameterType ) ; //default value
106
+ return Activator . CreateInstance ( parameterType ) ; //default value
87
107
}
88
108
89
109
/*
@@ -92,15 +112,15 @@ static object GetValue(JObject jobject, string name, ParameterInfo info)
92
112
* This currently supports all Guid format types stated in
93
113
* https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=net-5.0
94
114
*/
95
- if ( info . ParameterType == typeof ( Guid ) && value . Type == JTokenType . String )
115
+ if ( parameterType == typeof ( Guid ) && value . Type == JTokenType . String )
96
116
{
97
117
return Guid . Parse ( value . ToString ( ) ) ;
98
118
}
99
119
100
- if ( info . ParameterType . IsPrimitive || info . ParameterType == typeof ( string ) || info . ParameterType == typeof ( decimal ) )
101
- return Convert . ChangeType ( value , info . ParameterType ) ;
120
+ if ( parameterType . IsPrimitive || parameterType == typeof ( string ) || parameterType == typeof ( decimal ) )
121
+ return Convert . ChangeType ( value , parameterType ) ;
102
122
else
103
- return Convert . ChangeType ( JsonConvert . DeserializeObject ( value . ToString ( ) , info . ParameterType ) , info . ParameterType ) ;
123
+ return Convert . ChangeType ( JsonConvert . DeserializeObject ( value . ToString ( ) , parameterType ) , parameterType ) ;
104
124
}
105
125
106
126
static bool IsNullable ( Type type )
0 commit comments