Skip to content

Commit

Permalink
[release/8.0-staging] Support IValidatableObject for nested model typ…
Browse files Browse the repository at this point in the history
…es in options source gen (#94062)

Co-authored-by: Tarek Mahmoud Sayed <[email protected]>
  • Loading branch information
github-actions[bot] and tarekgh authored Oct 30, 2023
1 parent f78d53f commit b62c8ff
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/libraries/Microsoft.Extensions.Options/gen/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSy
var model = new ValidatedModel(
GetFQN(mt),
mt.Name,
false,
ModelSelfValidates(mt),
membersToValidate);

var validatorTypeName = "__" + mt.Name + "Validator__";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<IsPackable>true</IsPackable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>1</ServicingVersion>
<PackageDescription>Provides a strongly typed way of specifying and accessing settings using dependency injection.</PackageDescription>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,41 @@ partial class FirstValidator
}
}
namespace SelfValidation
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
internal sealed partial class __SecondModelValidator__
{
/// <summary>
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>Validation result.</returns>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "The created ValidationContext object is used in a way that never call reflection")]
public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.SecondModel options)
{
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);

context.MemberName = "P3";
context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}

(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));

return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
}
}
}
namespace SelfValidation
{
partial struct FirstValidator
{
Expand Down Expand Up @@ -1181,6 +1216,21 @@ partial struct FirstValidator
(builder ??= new()).AddResults(validationResults);
}

context.MemberName = "P2";
context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2";
validationResults.Clear();
validationAttributes.Clear();
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}

if (options.P2 is not null)
{
(builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2));
}

(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));

return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,39 @@ partial class FirstValidator
}
}
namespace SelfValidation
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
internal sealed partial class __SecondModelValidator__
{
/// <summary>
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>Validation result.</returns>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.SecondModel options)
{
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);

context.MemberName = "P3";
context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}

(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));

return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
}
}
}
namespace SelfValidation
{
partial struct FirstValidator
{
Expand All @@ -1123,6 +1156,21 @@ partial struct FirstValidator
(builder ??= new()).AddResults(validationResults);
}

context.MemberName = "P2";
context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2";
validationResults.Clear();
validationAttributes.Clear();
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
{
(builder ??= new()).AddResults(validationResults);
}

if (options.P2 is not null)
{
(builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2));
}

(builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));

return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ public void Invalid()
var firstModel = new FirstModel
{
P1 = "1234",
P2 = new SecondModel
{
P3 = "5678"
}
};

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

Utils.VerifyValidateOptionsResult(vr, 1, "P1");
Utils.VerifyValidateOptionsResult(vr, 2, "P3", "P1");
}

[Fact]
Expand All @@ -29,6 +33,10 @@ public void Valid()
var firstModel = new FirstModel
{
P1 = "12345",
P2 = new SecondModel
{
P3 = "67890"
}
};

var validator = default(FirstValidator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class FirstModel : IValidatableObject
[Required]
public string P1 { get; set; } = string.Empty;

[Required]
[ValidateObjectMembers]
public SecondModel P2 { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (P1.Length < 5)
Expand All @@ -26,6 +30,22 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
}
}

public class SecondModel : IValidatableObject
{
[Required]
public string P3 { get; set; } = string.Empty;

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (P3.Length < 5)
{
return new[] { new ValidationResult("P3 is not long enough") };
}

return Array.Empty<ValidationResult>();
}
}

[OptionsValidator]
public partial struct FirstValidator : IValidateOptions<FirstModel>
{
Expand Down

0 comments on commit b62c8ff

Please sign in to comment.