Skip to content

Commit 32e3c55

Browse files
tarekghliveans
authored andcommitted
Support IValidatableObject for nested model types in options source gen (dotnet#93952)
1 parent 2c41a61 commit 32e3c55

File tree

5 files changed

+128
-2
lines changed

5 files changed

+128
-2
lines changed

src/libraries/Microsoft.Extensions.Options/gen/Parser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSy
687687
var model = new ValidatedModel(
688688
GetFQN(mt),
689689
mt.Name,
690-
false,
690+
ModelSelfValidates(mt),
691691
membersToValidate);
692692

693693
var validatorTypeName = "__" + mt.Name + "Validator__";

src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,41 @@ partial class FirstValidator
11541154
}
11551155
}
11561156
namespace SelfValidation
1157+
{
1158+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
1159+
internal sealed partial class __SecondModelValidator__
1160+
{
1161+
/// <summary>
1162+
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
1163+
/// </summary>
1164+
/// <param name="name">The name of the options instance being validated.</param>
1165+
/// <param name="options">The options instance.</param>
1166+
/// <returns>Validation result.</returns>
1167+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
1168+
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
1169+
Justification = "The created ValidationContext object is used in a way that never call reflection")]
1170+
public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.SecondModel options)
1171+
{
1172+
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
1173+
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
1174+
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
1175+
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
1176+
1177+
context.MemberName = "P3";
1178+
context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3";
1179+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
1180+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
1181+
{
1182+
(builder ??= new()).AddResults(validationResults);
1183+
}
1184+
1185+
(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
1186+
1187+
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
1188+
}
1189+
}
1190+
}
1191+
namespace SelfValidation
11571192
{
11581193
partial struct FirstValidator
11591194
{
@@ -1181,6 +1216,21 @@ partial struct FirstValidator
11811216
(builder ??= new()).AddResults(validationResults);
11821217
}
11831218

1219+
context.MemberName = "P2";
1220+
context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2";
1221+
validationResults.Clear();
1222+
validationAttributes.Clear();
1223+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
1224+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
1225+
{
1226+
(builder ??= new()).AddResults(validationResults);
1227+
}
1228+
1229+
if (options.P2 is not null)
1230+
{
1231+
(builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2));
1232+
}
1233+
11841234
(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
11851235

11861236
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();

src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,39 @@ partial class FirstValidator
10981098
}
10991099
}
11001100
namespace SelfValidation
1101+
{
1102+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
1103+
internal sealed partial class __SecondModelValidator__
1104+
{
1105+
/// <summary>
1106+
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
1107+
/// </summary>
1108+
/// <param name="name">The name of the options instance being validated.</param>
1109+
/// <param name="options">The options instance.</param>
1110+
/// <returns>Validation result.</returns>
1111+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
1112+
public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.SecondModel options)
1113+
{
1114+
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
1115+
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
1116+
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
1117+
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
1118+
1119+
context.MemberName = "P3";
1120+
context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3";
1121+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
1122+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
1123+
{
1124+
(builder ??= new()).AddResults(validationResults);
1125+
}
1126+
1127+
(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
1128+
1129+
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
1130+
}
1131+
}
1132+
}
1133+
namespace SelfValidation
11011134
{
11021135
partial struct FirstValidator
11031136
{
@@ -1123,6 +1156,21 @@ partial struct FirstValidator
11231156
(builder ??= new()).AddResults(validationResults);
11241157
}
11251158

1159+
context.MemberName = "P2";
1160+
context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2";
1161+
validationResults.Clear();
1162+
validationAttributes.Clear();
1163+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
1164+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
1165+
{
1166+
(builder ??= new()).AddResults(validationResults);
1167+
}
1168+
1169+
if (options.P2 is not null)
1170+
{
1171+
(builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2));
1172+
}
1173+
11261174
(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
11271175

11281176
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();

src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ public void Invalid()
1515
var firstModel = new FirstModel
1616
{
1717
P1 = "1234",
18+
P2 = new SecondModel
19+
{
20+
P3 = "5678"
21+
}
1822
};
1923

2024
var validator = default(FirstValidator);
2125
var vr = validator.Validate("SelfValidation", firstModel);
2226

23-
Utils.VerifyValidateOptionsResult(vr, 1, "P1");
27+
Utils.VerifyValidateOptionsResult(vr, 2, "P3", "P1");
2428
}
2529

2630
[Fact]
@@ -29,6 +33,10 @@ public void Valid()
2933
var firstModel = new FirstModel
3034
{
3135
P1 = "12345",
36+
P2 = new SecondModel
37+
{
38+
P3 = "67890"
39+
}
3240
};
3341

3442
var validator = default(FirstValidator);

src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public class FirstModel : IValidatableObject
1515
[Required]
1616
public string P1 { get; set; } = string.Empty;
1717

18+
[Required]
19+
[ValidateObjectMembers]
20+
public SecondModel P2 { get; set; }
21+
1822
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
1923
{
2024
if (P1.Length < 5)
@@ -26,6 +30,22 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
2630
}
2731
}
2832

33+
public class SecondModel : IValidatableObject
34+
{
35+
[Required]
36+
public string P3 { get; set; } = string.Empty;
37+
38+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
39+
{
40+
if (P3.Length < 5)
41+
{
42+
return new[] { new ValidationResult("P3 is not long enough") };
43+
}
44+
45+
return Array.Empty<ValidationResult>();
46+
}
47+
}
48+
2949
[OptionsValidator]
3050
public partial struct FirstValidator : IValidateOptions<FirstModel>
3151
{

0 commit comments

Comments
 (0)