88using System . Linq ;
99using Microsoft . CodeAnalysis . CSharp ;
1010using System . IO ;
11+ using System . Collections . Generic ;
12+ using System ;
13+ using System . Globalization ;
1114
1215namespace Microsoft . AspNetCore . Http . ValidationsGenerator ;
1316
@@ -90,14 +93,14 @@ public GeneratedValidatableTypeInfo(
9093 {{ GeneratedCodeAttribute }}
9194 file class GeneratedValidatableInfoResolver : global::Microsoft.AspNetCore.Http.Validation.IValidatableInfoResolver
9295 {
93- public ValidatableTypeInfo? GetValidatableTypeInfo(Type type)
96+ public global::Microsoft.AspNetCore.Http.Validation. ValidatableTypeInfo? GetValidatableTypeInfo(Type type)
9497 {
9598 {{ EmitTypeChecks ( validatableTypes ) }}
9699 return null;
97100 }
98101
99102 // No-ops, rely on runtime code for ParameterInfo-based resolution
100- public ValidatableParameterInfo? GetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo)
103+ public global::Microsoft.AspNetCore.Http.Validation. ValidatableParameterInfo? GetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo)
101104 {
102105 return null;
103106 }
@@ -126,13 +129,13 @@ public static IServiceCollection AddValidation(this IServiceCollection services,
126129 {{ GeneratedCodeAttribute }}
127130 file static class ValidationAttributeCache
128131 {
129- private sealed record CacheKey(Type AttributeType, string [] Arguments, IReadOnlyDictionary<string, string > NamedArguments);
132+ private sealed record CacheKey(Type AttributeType, object [] Arguments, IReadOnlyDictionary<string, object > NamedArguments);
130133 private static readonly ConcurrentDictionary<CacheKey, ValidationAttribute> _cache = new();
131134
132135 public static ValidationAttribute? GetOrCreateValidationAttribute(
133136 Type attributeType,
134- string [] arguments,
135- IReadOnlyDictionary<string, string > namedArguments)
137+ object [] arguments,
138+ IReadOnlyDictionary<string, object > namedArguments)
136139 {
137140 var key = new CacheKey(attributeType, arguments, namedArguments);
138141 return _cache.GetOrAdd(key, static k =>
@@ -155,40 +158,62 @@ _ when typeof(ValidationAttribute).IsAssignableFrom(type) =>
155158 (ValidationAttribute)Activator.CreateInstance(type)!
156159 };
157160 }
161+ else if (type == typeof(CustomValidationAttribute) && args.Length == 2)
162+ {
163+ // CustomValidationAttribute requires special handling
164+ // First argument is a type, second is a method name
165+ if (args[0] is Type validatingType && args[1] is string methodName)
166+ {
167+ attribute = new CustomValidationAttribute(validatingType, methodName);
168+ }
169+ else
170+ {
171+ throw new ArgumentException($"Invalid arguments for CustomValidationAttribute: Type and method name required");
172+ }
173+ }
158174 else if (type == typeof(StringLengthAttribute))
159175 {
160- if (!int.TryParse(args[0], out var maxLength))
176+ if (args[0] is int maxLength)
177+ attribute = new StringLengthAttribute(maxLength);
178+ else
161179 throw new ArgumentException($"Invalid maxLength value for StringLengthAttribute: {args[0]}");
162- attribute = new StringLengthAttribute(maxLength);
163180 }
164181 else if (type == typeof(MinLengthAttribute))
165182 {
166- if (!int.TryParse(args[0], out var length))
183+ if (args[0] is int length)
184+ attribute = new MinLengthAttribute(length);
185+ else
167186 throw new ArgumentException($"Invalid length value for MinLengthAttribute: {args[0]}");
168- attribute = new MinLengthAttribute(length);
169187 }
170188 else if (type == typeof(MaxLengthAttribute))
171189 {
172- if (!int.TryParse(args[0], out var length))
190+ if (args[0] is int length)
191+ attribute = new MaxLengthAttribute(length);
192+ else
173193 throw new ArgumentException($"Invalid length value for MaxLengthAttribute: {args[0]}");
174- attribute = new MaxLengthAttribute(length);
175194 }
176195 else if (type == typeof(RangeAttribute) && args.Length == 2)
177196 {
178- if (int.TryParse( args[0], out var min) && int.TryParse( args[1], out var max) )
197+ if (args[0] is int min && args[1] is int max)
179198 attribute = new RangeAttribute(min, max);
180- else if (double.TryParse( args[0], out var dmin) && double.TryParse( args[1], out var dmax) )
199+ else if (args[0] is double dmin && args[1] is double dmax)
181200 attribute = new RangeAttribute(dmin, dmax);
182201 else
183202 throw new ArgumentException($"Invalid range values for RangeAttribute: {args[0]}, {args[1]}");
184203 }
185204 else if (type == typeof(RegularExpressionAttribute))
186205 {
187- attribute = new RegularExpressionAttribute(args[0]);
206+ if (args[0] is string pattern)
207+ attribute = new RegularExpressionAttribute(pattern);
208+ else
209+ throw new ArgumentException($"Invalid pattern for RegularExpressionAttribute: {args[0]}");
188210 }
189211 else if (type == typeof(CompareAttribute))
190212 {
191- attribute = new CompareAttribute(args[0]);
213+ if (args[0] is string otherProperty)
214+ attribute = new CompareAttribute(otherProperty);
215+ else
216+ throw new ArgumentException($"Invalid otherProperty for CompareAttribute: {args[0]}");
192217 }
193218 else if (typeof(ValidationAttribute).IsAssignableFrom(type))
194219 {
@@ -209,7 +234,16 @@ _ when typeof(ValidationAttribute).IsAssignableFrom(type) =>
209234 {
210235 try
211236 {
212- convertedArgs[i] = Convert.ChangeType(args[i], parameters[i].ParameterType);
237+ if (args[i] != null && args[i].GetType() == parameters[i].ParameterType)
238+ {
239+ // Type already matches, use as-is
240+ convertedArgs[i] = args[i];
241+ }
242+ else
243+ {
244+ // Try to convert
245+ convertedArgs[i] = Convert.ChangeType(args[i], parameters[i].ParameterType);
246+ }
213247 }
214248 catch
215249 {
@@ -244,8 +278,16 @@ _ when typeof(ValidationAttribute).IsAssignableFrom(type) =>
244278 {
245279 try
246280 {
247- var convertedValue = Convert.ChangeType(namedArg.Value, prop.PropertyType);
248- prop.SetValue(attribute, convertedValue);
281+ if (namedArg.Value != null && namedArg.Value.GetType() == prop.PropertyType)
282+ {
283+ // Type already matches, use as-is
284+ prop.SetValue(attribute, namedArg.Value);
285+ }
286+ else
287+ {
288+ // Try to convert
289+ prop.SetValue(attribute, Convert.ChangeType(namedArg.Value, prop.PropertyType));
290+ }
249291 }
250292 catch (Exception ex)
251293 {
@@ -260,6 +302,112 @@ _ when typeof(ValidationAttribute).IsAssignableFrom(type) =>
260302 }
261303}
262304""" ;
305+
306+ private static string EmitValidationAttributeForCreate ( ValidationAttribute attr )
307+ {
308+ // Process constructor arguments - convert to appropriate typed objects
309+ var processedArgs = new List < string > ( attr . Arguments . Count ) ;
310+
311+ foreach ( var arg in attr . Arguments )
312+ {
313+ // Handle different types of arguments
314+ if ( arg . StartsWith ( "\" " , StringComparison . OrdinalIgnoreCase ) && arg . EndsWith ( "\" " , StringComparison . OrdinalIgnoreCase ) )
315+ {
316+ // String literal - remove quotes and pass as object
317+ var stringValue = arg . Substring ( 1 , arg . Length - 2 ) . Replace ( "\\ \" " , "\" " ) ;
318+ processedArgs . Add ( $ "\" { stringValue } \" ") ;
319+ }
320+ else if ( arg . StartsWith ( "typeof(" , StringComparison . OrdinalIgnoreCase ) && arg . EndsWith ( ")" , StringComparison . OrdinalIgnoreCase ) )
321+ {
322+ // Type argument - pass directly
323+ processedArgs . Add ( arg ) ;
324+ }
325+ else if ( int . TryParse ( arg , out var intValue ) )
326+ {
327+ // Integer
328+ processedArgs . Add ( intValue . ToString ( CultureInfo . InvariantCulture ) ) ;
329+ }
330+ else if ( double . TryParse ( arg , out var doubleValue ) )
331+ {
332+ // Double
333+ processedArgs . Add ( doubleValue . ToString ( CultureInfo . InvariantCulture ) + "d" ) ;
334+ }
335+ else if ( bool . TryParse ( arg , out var boolValue ) )
336+ {
337+ // Boolean
338+ processedArgs . Add ( boolValue . ToString ( ) . ToLowerInvariant ( ) ) ;
339+ }
340+ else if ( arg == "null" )
341+ {
342+ // Null
343+ processedArgs . Add ( "null" ) ;
344+ }
345+ else
346+ {
347+ // Default to string for anything else
348+ processedArgs . Add ( $ "\" { arg . Replace ( "\" " , "\\ \" " ) } \" ") ;
349+ }
350+ }
351+
352+ var args = attr . Arguments . Count > 0
353+ ? $ "new object[] {{ { string . Join ( ", " , processedArgs ) } }}"
354+ : "Array.Empty<object>()" ;
355+
356+ // Process named arguments - ensure proper formatting for object dictionary
357+ var namedArgsParts = new List < string > ( attr . NamedArguments . Count ) ;
358+ foreach ( var pair in attr . NamedArguments )
359+ {
360+ // Convert the value based on its format
361+ var valueStr = pair . Value ;
362+ string objectValue ;
363+
364+ if ( valueStr . StartsWith ( "\" " , StringComparison . OrdinalIgnoreCase ) && valueStr . EndsWith ( "\" " , StringComparison . OrdinalIgnoreCase ) )
365+ {
366+ // String literal
367+ objectValue = valueStr ;
368+ }
369+ else if ( valueStr . StartsWith ( "typeof(" , StringComparison . OrdinalIgnoreCase ) && valueStr . EndsWith ( ")" , StringComparison . OrdinalIgnoreCase ) )
370+ {
371+ // Type argument
372+ objectValue = valueStr ;
373+ }
374+ else if ( int . TryParse ( valueStr , out _ ) )
375+ {
376+ // Integer
377+ objectValue = valueStr ;
378+ }
379+ else if ( double . TryParse ( valueStr , out _ ) )
380+ {
381+ // Double
382+ objectValue = valueStr + "d" ;
383+ }
384+ else if ( bool . TryParse ( valueStr , out var boolVal ) )
385+ {
386+ // Boolean
387+ objectValue = boolVal . ToString ( ) . ToLowerInvariant ( ) ;
388+ }
389+ else if ( valueStr == "null" )
390+ {
391+ // Null
392+ objectValue = "null" ;
393+ }
394+ else
395+ {
396+ // Default to string for anything else
397+ objectValue = $ "\" { valueStr . Replace ( "\" " , "\\ \" " ) } \" ";
398+ }
399+
400+ namedArgsParts . Add ( $ "{{ \" { pair . Key } \" , { objectValue } }}") ;
401+ }
402+
403+ var namedArgs = attr . NamedArguments . Count > 0
404+ ? $ "new Dictionary<string, object> {{ { string . Join ( ", " , namedArgsParts ) } }}"
405+ : "new Dictionary<string, object>()" ;
406+
407+ // Use string interpolation with @ to prevent escaping issues in the error message
408+ return $@ "ValidationAttributeCache.GetOrCreateValidationAttribute(typeof({ attr . ClassName } ), { args } , { namedArgs } ) ?? throw new InvalidOperationException(@""Failed to create validation attribute { attr . ClassName } "")";
409+ }
410+
263411 private static string EmitTypeChecks ( ImmutableArray < ValidatableType > validatableTypes )
264412 {
265413 var sw = new StringWriter ( ) ;
@@ -318,19 +466,6 @@ private static string EmitValidatableMemberForCreate(ValidatableProperty member)
318466""" ;
319467 }
320468
321- private static string EmitValidationAttributeForCreate ( ValidationAttribute attr )
322- {
323- var args = attr . Arguments . Count > 0
324- ? $ "new string[] {{ { string . Join ( ", " , attr . Arguments . Select ( a => $@ """{ a } """))} }}"
325- : "Array.Empty<string>()" ;
326-
327- var namedArgs = attr. NamedArguments. Count > 0
328- ? $ "new Dictionary<string, string> {{ { string . Join ( ", " , attr . NamedArguments . Select ( x => $@ "{{ ""{ x . Key } "", { x . Value } }}") ) } }}"
329- : "new Dictionary<string, string>()" ;
330-
331- return $"ValidationAttributeCache.GetOrCreateValidationAttribute(typeof({attr.ClassName}), {args}, {namedArgs}) ?? throw new InvalidOperationException(\" Failed to create validation attribute {attr.ClassName}\" )" ;
332- }
333-
334469 private static string SanitizeTypeName ( string typeName )
335470 {
336471 // Replace invalid characters with underscores and remove generic notation
0 commit comments