1
1
using System . Diagnostics . CodeAnalysis ;
2
- using System . Linq . Expressions ;
3
2
using System . Reflection ;
4
3
5
4
using Microsoft . Extensions . Configuration ;
@@ -46,13 +45,16 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection<As
46
45
if ( toType . IsArray )
47
46
return CreateArray ( ) ;
48
47
48
+ // Only try to call ctor when type is explicitly specified in _section
49
+ if ( TryCallCtorExplicit ( _section , resolutionContext , out var ctorResult ) )
50
+ return ctorResult ;
51
+
49
52
if ( IsContainer ( toType , out var elementType ) && TryCreateContainer ( out var container ) )
50
53
return container ;
51
54
52
- if ( TryBuildCtorExpression ( _section , toType , resolutionContext , out var ctorExpression ) )
53
- {
54
- return Expression . Lambda < Func < object > > ( ctorExpression ) . Compile ( ) . Invoke ( ) ;
55
- }
55
+ // Without a type explicitly specified, attempt to call ctor of toType
56
+ if ( TryCallCtorImplicit ( _section , toType , resolutionContext , out ctorResult ) )
57
+ return ctorResult ;
56
58
57
59
// MS Config binding can work with a limited set of primitive types and collections
58
60
return _section . Get ( toType ) ;
@@ -76,33 +78,41 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result)
76
78
{
77
79
result = null ;
78
80
79
- if ( toType . GetConstructor ( Type . EmptyTypes ) == null )
80
- return false ;
81
-
82
- // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
83
- var addMethod = toType . GetMethods ( ) . FirstOrDefault ( m => ! m . IsStatic && m . Name == "Add" && m . GetParameters ( ) . Length == 1 && m . GetParameters ( ) [ 0 ] . ParameterType == elementType ) ;
84
- if ( addMethod == null )
85
- return false ;
81
+ if ( IsConstructableDictionary ( toType , elementType , out var concreteType , out var keyType , out var valueType , out var addMethod ) )
82
+ {
83
+ result = Activator . CreateInstance ( concreteType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { concreteType } ") ;
86
84
87
- var configurationElements = _section . GetChildren ( ) . ToArray ( ) ;
88
- result = Activator . CreateInstance ( toType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { toType } ") ;
85
+ foreach ( var section in _section . GetChildren ( ) )
86
+ {
87
+ var argumentValue = ConfigurationReader . GetArgumentValue ( section , _configurationAssemblies ) ;
88
+ var key = new StringArgumentValue ( section . Key ) . ConvertTo ( keyType , resolutionContext ) ;
89
+ var value = argumentValue . ConvertTo ( valueType , resolutionContext ) ;
90
+ addMethod . Invoke ( result , new [ ] { key , value } ) ;
91
+ }
92
+ return true ;
93
+ }
94
+ else if ( IsConstructableContainer ( toType , elementType , out concreteType , out addMethod ) )
95
+ {
96
+ result = Activator . CreateInstance ( concreteType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { concreteType } ") ;
89
97
90
- for ( int i = 0 ; i < configurationElements . Length ; ++ i )
98
+ foreach ( var section in _section . GetChildren ( ) )
99
+ {
100
+ var argumentValue = ConfigurationReader . GetArgumentValue ( section , _configurationAssemblies ) ;
101
+ var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
102
+ addMethod . Invoke ( result , new [ ] { value } ) ;
103
+ }
104
+ return true ;
105
+ }
106
+ else
91
107
{
92
- var argumentValue = ConfigurationReader . GetArgumentValue ( configurationElements [ i ] , _configurationAssemblies ) ;
93
- var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
94
- addMethod . Invoke ( result , new [ ] { value } ) ;
108
+ return false ;
95
109
}
96
-
97
- return true ;
98
110
}
99
111
}
100
112
101
- internal static bool TryBuildCtorExpression (
102
- IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
113
+ bool TryCallCtorExplicit (
114
+ IConfigurationSection section , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out object ? value )
103
115
{
104
- ctorExpression = null ;
105
-
106
116
var typeDirective = section . GetValue < string > ( "$type" ) switch
107
117
{
108
118
not null => "$type" ,
@@ -116,21 +126,39 @@ internal static bool TryBuildCtorExpression(
116
126
var type = typeDirective switch
117
127
{
118
128
not null => Type . GetType ( section . GetValue < string > ( typeDirective ) ! , throwOnError : false ) ,
119
- null => parameterType ,
129
+ null => null ,
120
130
} ;
121
131
122
132
if ( type is null or { IsAbstract : true } )
123
133
{
134
+ value = null ;
124
135
return false ;
125
136
}
137
+ else
138
+ {
139
+ var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
140
+ . ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
141
+ return TryCallCtor ( type , suppliedArguments , resolutionContext , out value ) ;
142
+ }
126
143
127
- var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
144
+ }
145
+
146
+ bool TryCallCtorImplicit (
147
+ IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , out object ? value )
148
+ {
149
+ var suppliedArguments = section . GetChildren ( )
128
150
. ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
151
+ return TryCallCtor ( parameterType , suppliedArguments , resolutionContext , out value ) ;
152
+ }
153
+
154
+ bool TryCallCtor ( Type type , Dictionary < string , IConfigurationSection > suppliedArguments , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out object ? value )
155
+ {
156
+ value = null ;
129
157
130
158
if ( suppliedArguments . Count == 0 &&
131
159
type . GetConstructor ( Type . EmptyTypes ) is ConstructorInfo parameterlessCtor )
132
160
{
133
- ctorExpression = Expression . New ( parameterlessCtor ) ;
161
+ value = parameterlessCtor . Invoke ( [ ] ) ;
134
162
return true ;
135
163
}
136
164
@@ -163,76 +191,126 @@ where gr.All(z => z.argumentBindResult.success)
163
191
return false ;
164
192
}
165
193
166
- var ctorArguments = new List < Expression > ( ) ;
167
- foreach ( var argumentValue in ctor . ArgumentValues )
194
+ var ctorArguments = new object ? [ ctor . ArgumentValues . Count ] ;
195
+ for ( var i = 0 ; i < ctor . ArgumentValues . Count ; i ++ )
168
196
{
169
- if ( TryBindToCtorArgument ( argumentValue . Value , argumentValue . Type , resolutionContext , out var argumentExpression ) )
197
+ var argument = ctor . ArgumentValues [ i ] ;
198
+ var valueValue = argument . Value ;
199
+ if ( valueValue is IConfigurationSection s )
170
200
{
171
- ctorArguments . Add ( argumentExpression ) ;
172
- }
173
- else
174
- {
175
- return false ;
201
+ var argumentValue = ConfigurationReader . GetArgumentValue ( s , _configurationAssemblies ) ;
202
+ valueValue = argumentValue . ConvertTo ( argument . Type , resolutionContext ) ;
176
203
}
204
+ ctorArguments [ i ] = valueValue ;
177
205
}
178
206
179
- ctorExpression = Expression . New ( ctor . ConstructorInfo , ctorArguments ) ;
207
+ value = ctor . ConstructorInfo . Invoke ( ctorArguments ) ;
180
208
return true ;
209
+ }
181
210
182
- static bool TryBindToCtorArgument ( object value , Type type , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out Expression ? argumentExpression )
211
+ static bool IsContainer ( Type type , [ NotNullWhen ( true ) ] out Type ? elementType )
212
+ {
213
+ elementType = null ;
214
+ if ( type . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
183
215
{
184
- argumentExpression = null ;
185
-
186
- if ( value is IConfigurationSection s )
216
+ elementType = type . GetGenericArguments ( ) [ 0 ] ;
217
+ return true ;
218
+ }
219
+ foreach ( var iface in type . GetInterfaces ( ) )
220
+ {
221
+ if ( iface . IsGenericType )
187
222
{
188
- if ( s . Value is string argValue )
223
+ if ( iface . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
189
224
{
190
- var stringArgumentValue = new StringArgumentValue ( argValue ) ;
191
- try
192
- {
193
- argumentExpression = Expression . Constant (
194
- stringArgumentValue . ConvertTo ( type , resolutionContext ) ,
195
- type ) ;
196
-
197
- return true ;
198
- }
199
- catch ( Exception )
200
- {
201
- return false ;
202
- }
225
+ elementType = iface . GetGenericArguments ( ) [ 0 ] ;
226
+ return true ;
203
227
}
204
- else if ( s . GetChildren ( ) . Any ( ) )
205
- {
206
- if ( TryBuildCtorExpression ( s , type , resolutionContext , out var ctorExpression ) )
207
- {
208
- argumentExpression = ctorExpression ;
209
- return true ;
210
- }
228
+ }
229
+ }
211
230
212
- return false ;
231
+ return false ;
232
+ }
233
+
234
+ static bool IsConstructableDictionary ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out Type ? keyType , [ NotNullWhen ( true ) ] out Type ? valueType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
235
+ {
236
+ concreteType = null ;
237
+ keyType = null ;
238
+ valueType = null ;
239
+ addMethod = null ;
240
+ if ( ! elementType . IsGenericType || elementType . GetGenericTypeDefinition ( ) != typeof ( KeyValuePair < , > ) )
241
+ {
242
+ return false ;
243
+ }
244
+ var argumentTypes = elementType . GetGenericArguments ( ) ;
245
+ keyType = argumentTypes [ 0 ] ;
246
+ valueType = argumentTypes [ 1 ] ;
247
+ if ( type . IsAbstract )
248
+ {
249
+ concreteType = typeof ( Dictionary < , > ) . MakeGenericType ( argumentTypes ) ;
250
+ if ( ! type . IsAssignableFrom ( concreteType ) )
251
+ {
252
+ return false ;
253
+ }
254
+ }
255
+ else
256
+ {
257
+ concreteType = type ;
258
+ }
259
+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
260
+ {
261
+ return false ;
262
+ }
263
+ foreach ( var method in concreteType . GetMethods ( ) )
264
+ {
265
+ if ( ! method . IsStatic && method . Name == "Add" )
266
+ {
267
+ var parameters = method . GetParameters ( ) ;
268
+ if ( parameters . Length == 2 && parameters [ 0 ] . ParameterType == keyType && parameters [ 1 ] . ParameterType == valueType )
269
+ {
270
+ addMethod = method ;
271
+ return true ;
213
272
}
214
273
}
215
-
216
- argumentExpression = Expression . Constant ( value , type ) ;
217
- return true ;
218
274
}
275
+ return false ;
219
276
}
220
277
221
- static bool IsContainer ( Type type , [ NotNullWhen ( true ) ] out Type ? elementType )
278
+ static bool IsConstructableContainer ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
222
279
{
223
- elementType = null ;
224
- foreach ( var iface in type . GetInterfaces ( ) )
280
+ addMethod = null ;
281
+ if ( type . IsAbstract )
225
282
{
226
- if ( iface . IsGenericType )
283
+ concreteType = typeof ( List < > ) . MakeGenericType ( elementType ) ;
284
+ if ( ! type . IsAssignableFrom ( concreteType ) )
227
285
{
228
- if ( iface . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
286
+ concreteType = typeof ( HashSet < > ) . MakeGenericType ( elementType ) ;
287
+ if ( ! type . IsAssignableFrom ( concreteType ) )
229
288
{
230
- elementType = iface . GetGenericArguments ( ) [ 0 ] ;
289
+ concreteType = null ;
290
+ return false ;
291
+ }
292
+ }
293
+ }
294
+ else
295
+ {
296
+ concreteType = type ;
297
+ }
298
+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
299
+ {
300
+ return false ;
301
+ }
302
+ foreach ( var method in concreteType . GetMethods ( ) )
303
+ {
304
+ if ( ! method . IsStatic && method . Name == "Add" )
305
+ {
306
+ var parameters = method . GetParameters ( ) ;
307
+ if ( parameters . Length == 1 && parameters [ 0 ] . ParameterType == elementType )
308
+ {
309
+ addMethod = method ;
231
310
return true ;
232
311
}
233
312
}
234
313
}
235
-
236
314
return false ;
237
315
}
238
316
}
0 commit comments