未验证 提交 bc43b7ec 编写于 作者: T Tarek Mahmoud Sayed 提交者: GitHub

Support new Options attributes in the Runtime (#90275)

上级 867e1852
......@@ -2,9 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Options
{
......@@ -52,19 +56,81 @@ public ValidateOptionsResult Validate(string? name, TOptions options)
ThrowHelper.ThrowIfNull(options);
var validationResults = new List<ValidationResult>();
if (Validator.TryValidateObject(options, new ValidationContext(options), validationResults, validateAllProperties: true))
HashSet<object>? visited = null;
List<string>? errors = null;
if (TryValidateOptions(options, options.GetType().Name, validationResults, ref errors, ref visited))
{
return ValidateOptionsResult.Success;
}
string typeName = options.GetType().Name;
var errors = new List<string>();
foreach (ValidationResult result in validationResults)
Debug.Assert(errors is not null && errors.Count > 0);
return ValidateOptionsResult.Fail(errors);
}
[RequiresUnreferencedCode("This method on this type will walk through all properties of the passed in options object, and its type cannot be " +
"statically analyzed so its members may be trimmed.")]
private static bool TryValidateOptions(object options, string qualifiedName, List<ValidationResult> results, ref List<string>? errors, ref HashSet<object>? visited)
{
Debug.Assert(options is not null);
if (visited is not null && visited.Contains(options))
{
errors.Add($"DataAnnotation validation failed for '{typeName}' members: '{string.Join(",", result.MemberNames)}' with the error: '{result.ErrorMessage}'.");
return true;
}
return ValidateOptionsResult.Fail(errors);
results.Clear();
bool res = Validator.TryValidateObject(options, new ValidationContext(options), results, validateAllProperties: true);
if (!res)
{
errors ??= new List<string>();
foreach (ValidationResult result in results!)
{
errors.Add($"DataAnnotation validation failed for '{qualifiedName}' members: '{string.Join(",", result.MemberNames)}' with the error: '{result.ErrorMessage}'.");
}
}
foreach (PropertyInfo propertyInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (propertyInfo.GetMethod is null)
{
continue;
}
object? value = propertyInfo!.GetValue(options);
if (value is null)
{
continue;
}
if (propertyInfo.GetCustomAttribute<ValidateObjectMembersAttribute>() is not null)
{
visited ??= new HashSet<object>(ReferenceEqualityComparer.Instance);
visited.Add(options);
results ??= new List<ValidationResult>();
res = TryValidateOptions(value, $"{qualifiedName}.{propertyInfo.Name}", results, ref errors, ref visited) && res;
}
else if (value is IEnumerable enumerable &&
propertyInfo.GetCustomAttribute<ValidateEnumeratedItemsAttribute>() is not null)
{
visited ??= new HashSet<object>(ReferenceEqualityComparer.Instance);
visited.Add(options);
results ??= new List<ValidationResult>();
int index = 0;
foreach (object item in enumerable)
{
res = TryValidateOptions(item, $"{qualifiedName}.{propertyInfo.Name}[{index++}]", results, ref errors, ref visited) && res;
}
}
}
return res;
}
}
}
......@@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ReferenceEqualityComparer.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
......
......@@ -276,7 +276,7 @@ private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool s
var memberInfo = GetMemberInfo(member, speculate, location, validatorType);
if (memberInfo is not null)
{
if (member.DeclaredAccessibility != Accessibility.Public && member.DeclaredAccessibility != Accessibility.Internal)
if (member.DeclaredAccessibility != Accessibility.Public)
{
Diag(DiagDescriptors.MemberIsInaccessible, member.Locations.First(), member.Name);
continue;
......@@ -297,6 +297,8 @@ private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool s
case IPropertySymbol prop:
memberType = prop.Type;
break;
/* The runtime doesn't support fields validation yet. If we allow that in the future, we need to add the following code back.
case IFieldSymbol field:
if (field.AssociatedSymbol is not null)
{
......@@ -306,6 +308,7 @@ private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool s
memberType = field.Type;
break;
*/
default:
// we only care about properties and fields
return null;
......
......@@ -143,7 +143,7 @@ public class FirstModel
public class SecondModel
{
[Required]
public string? P3;
public string? P3 { get; set; }
}
[OptionsValidator]
......@@ -205,7 +205,7 @@ public class FirstModel
public const string? P1;
[ValidateObjectMembers]
public static SecondModel P2 = new();
public static SecondModel P2 { get; set; } = new();
[ValidateEnumeratedItems]
public static System.Collections.Generic.IList<SecondModel>? P3 { get; set; }
......@@ -217,7 +217,7 @@ public class FirstModel
public class SecondModel
{
[Required]
public string? P3;
public string? P3 { get; set; }
}
[OptionsValidator]
......@@ -226,7 +226,7 @@ public partial class FirstModelValidator : IValidateOptions<FirstModel>
}
");
Assert.Equal(4, d.Count);
Assert.Equal(3, d.Count);
Assert.All(d, x => Assert.Equal(DiagDescriptors.CantValidateStaticOrConstMember.Id, x.Id));
Assert.All(d, x => Assert.Equal(DiagnosticSeverity.Warning, x.DefaultSeverity));
}
......@@ -259,13 +259,13 @@ public async Task InvalidValidatorInterface()
public class FirstModel
{
[Required]
public string? P1;
public string? P1 { get; set; }
}
public class SecondModel
{
[Required]
public string? P2;
public string? P2 { get; set; }
}
[OptionsValidator]
......@@ -290,13 +290,13 @@ public async Task NotValidator()
public class FirstModel
{
[ValidateObjectMembers(typeof(SecondValidator)]
public SecondModel? P1;
public SecondModel? P1 { get; set; }
}
public class SecondModel
{
[Required]
public string? P2;
public string? P2 { get; set; }
}
[OptionsValidator]
......@@ -320,16 +320,16 @@ public async Task ValidatorAlreadyImplementValidateFunction()
public class FirstModel
{
[Required]
public string? P1;
public string? P1 { get; set; }
[ValidateObjectMembers(typeof(SecondValidator)]
public SecondModel? P2;
public SecondModel? P2 { get; set; }
}
public class SecondModel
{
[Required]
public string? P3;
public string? P3 { get; set; }
}
[OptionsValidator]
......@@ -358,13 +358,13 @@ public async Task NullValidator()
public class FirstModel
{
[ValidateObjectMembers(null!)]
public SecondModel? P1;
public SecondModel? P1 { get; set; }
}
public class SecondModel
{
[Required]
public string? P2;
public string? P2 { get; set; }
}
[OptionsValidator]
......@@ -389,16 +389,16 @@ public async Task NoSimpleValidatorConstructor()
public class FirstModel
{
[Required]
public string? P1;
public string? P1 { get; set; }
[ValidateObjectMembers(typeof(SecondValidator)]
public SecondModel? P2;
public SecondModel? P2 { get; set; }
}
public class SecondModel
{
[Required]
public string? P3;
public string? P3 { get; set; }
}
[OptionsValidator]
......@@ -426,7 +426,7 @@ public async Task NoStaticValidator()
public class FirstModel
{
[Required]
public string P1;
public string P1 { get; set; }
}
[OptionsValidator]
......@@ -461,15 +461,15 @@ public class FirstModel<T>
{
[Required]
[ValidateObjectMembers]
public T? P1;
public T? P1 { get; set; }
[ValidateObjectMembers]
[Required]
public T[]? P2;
public T[]? P2 { get; set; }
[ValidateObjectMembers]
[Required]
public System.Collections.Generics.IList<T> P3 = null!;
public System.Collections.Generics.IList<T> P3 { get; set;} = null!;
}
[OptionsValidator]
......@@ -492,19 +492,19 @@ public class FirstModel<T>
{
[Required]
[ValidateObjectMembers]
public T? P1;
public T? P1 { get; set; }
[ValidateObjectMembers]
[Required]
public T[]? P2;
public T[]? P2 { get; set; }
[ValidateObjectMembers]
[Required]
public int[]? P3;
public int[]? P3 { get; set; }
[ValidateObjectMembers]
[Required]
public System.Collections.Generics.IList<T>? P4;
public System.Collections.Generics.IList<T>? P4 { get; set; }
}
[OptionsValidator]
......@@ -528,12 +528,12 @@ public class FirstModel
{
[Required]
[ValidateObjectMembers]
public SecondModel? P1;
public SecondModel? P1 { get; set; }
}
public class SecondModel
{
public string P2;
public string P2 { get; set; };
}
[OptionsValidator]
......@@ -1078,13 +1078,13 @@ public async Task NotValidatorInEnumeration()
public class FirstModel
{
[ValidateEnumeratedItems(typeof(SecondValidator)]
public SecondModel[]? P1;
public SecondModel[]? P1 { get; set; }
}
public class SecondModel
{
[Required]
public string? P2;
public string? P2 { get; set; }
}
[OptionsValidator]
......@@ -1108,13 +1108,13 @@ public async Task NullValidatorInEnumeration()
public class FirstModel
{
[ValidateEnumeratedItems(null!)]
public SecondModel[]? P1;
public SecondModel[]? P1 { get; set; }
}
public class SecondModel
{
[Required]
public string? P2;
public string? P2 { get; set; }
}
[OptionsValidator]
......@@ -1139,16 +1139,16 @@ public async Task NoSimpleValidatorConstructorInEnumeration()
public class FirstModel
{
[Required]
public string? P1;
public string? P1 { get; set; }
[ValidateEnumeratedItems(typeof(SecondValidator)]
public SecondModel[]? P2;
public SecondModel[]? P2 { get; set; }
}
public class SecondModel
{
[Required]
public string? P3;
public string? P3 { get; set; }
}
[OptionsValidator]
......@@ -1507,15 +1507,15 @@ public class FirstModel<T>
{
[Required]
[ValidateEnumeratedItems]
public T[]? P1;
public T[]? P1 { get; set; }
[ValidateEnumeratedItems]
[Required]
public T[]? P2;
public T[]? P2 { get; set; }
[ValidateEnumeratedItems]
[Required]
public System.Collections.Generic.IList<T> P3 = null!;
public System.Collections.Generic.IList<T> P3 { get; set; } = null!;
}
[OptionsValidator]
......@@ -1538,15 +1538,15 @@ public class FirstModel<T>
{
[ValidateEnumeratedItems]
[Required]
public T[]? P1;
public T[]? P1 { get; set; }
[ValidateEnumeratedItems]
[Required]
public int[]? P2;
public int[]? P2 { get; set; }
[ValidateEnumeratedItems]
[Required]
public System.Collections.Generic.IList<T>? P3;
public System.Collections.Generic.IList<T>? P3 { get; set; }
}
[OptionsValidator]
......@@ -1569,7 +1569,7 @@ public class FirstModel
{
[Required]
[ValidateEnumeratedItems]
public int P1;
public int P1 { get; set; }
}
[OptionsValidator]
......
......@@ -20,6 +20,10 @@
<ProjectReference Include="$(LibrariesProjectRoot)System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options.DataAnnotations\src\Microsoft.Extensions.Options.DataAnnotations.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Reference Include="System.ComponentModel.DataAnnotations" />
</ItemGroup>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Gen.OptionsValidation.Unit.Test
{
public class OptionsRuntimeTests
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TestValidationSuccessResults()
{
MyOptions options = new()
{
Name = "T",
Phone = "P",
Age = 30,
Nested = new()
{
Tall = 10,
Id = "1",
Children = new()
{
new ChildOptions() { Name = "C1" },
new ChildOptions() { Name = "C2" }
},
NestedList = new()
{
new NestedOptions() { Tall = 5, Id = "1" },
new NestedOptions() { Tall = 6, Id = "2" },
new NestedOptions() { Tall = 7, Id = "3" }
}
}
};
MySourceGenOptionsValidator sourceGenOptionsValidator = new();
DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
ValidateOptionsResult result = sourceGenOptionsValidator.Validate("MyOptions", options);
Assert.True(result.Succeeded);
result = dataAnnotationValidateOptions.Validate("MyOptions", options);
Assert.True(result.Succeeded);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TestBasicDataAnnotationFailures()
{
MyOptions options = new();
MySourceGenOptionsValidator sourceGenOptionsValidator = new();
DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
Assert.True(result1.Failed);
Assert.Equal(new List<string>
{
"Age: The field MyOptions.Age must be between 0 and 100.",
"Name: The MyOptions.Name field is required.",
"Phone: The MyOptions.Phone field is required."
},
result1.Failures);
ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
Assert.True(result2.Failed);
Assert.Equal(new List<string>
{
"DataAnnotation validation failed for 'MyOptions' members: 'Age' with the error: 'The field Age must be between 0 and 100.'.",
"DataAnnotation validation failed for 'MyOptions' members: 'Name' with the error: 'The Name field is required.'.",
"DataAnnotation validation failed for 'MyOptions' members: 'Phone' with the error: 'The Phone field is required.'."
},
result2.Failures);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TestValidationWithNestedTypes()
{
MyOptions options = new()
{
Name = "T",
Phone = "P",
Age = 30,
Nested = new()
{
Tall = 20,
}
};
MySourceGenOptionsValidator sourceGenOptionsValidator = new();
DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
Assert.True(result1.Failed);
Assert.Equal(new List<string>
{
"Tall: The field MyOptions.Nested.Tall must be between 0 and 10.",
"Id: The MyOptions.Nested.Id field is required.",
},
result1.Failures);
ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
Assert.True(result2.Failed);
Assert.Equal(new List<string>
{
"DataAnnotation validation failed for 'MyOptions.Nested' members: 'Tall' with the error: 'The field Tall must be between 0 and 10.'.",
"DataAnnotation validation failed for 'MyOptions.Nested' members: 'Id' with the error: 'The Id field is required.'.",
},
result2.Failures);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TestValidationWithEnumeration()
{
MyOptions options = new()
{
Name = "T",
Phone = "P",
Age = 30,
Nested = new()
{
Tall = 10,
Id = "1",
Children = new()
{
new ChildOptions(),
new ChildOptions(),
new ChildOptions()
}
}
};
MySourceGenOptionsValidator sourceGenOptionsValidator = new();
DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
Assert.True(result1.Failed);
Assert.Equal(new List<string>
{
"Name: The MyOptions.Nested.Children[0].Name field is required.",
"Name: The MyOptions.Nested.Children[1].Name field is required.",
"Name: The MyOptions.Nested.Children[2].Name field is required.",
},
result1.Failures);
ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
Assert.True(result2.Failed);
Assert.Equal(new List<string>
{
"DataAnnotation validation failed for 'MyOptions.Nested.Children[0]' members: 'Name' with the error: 'The Name field is required.'.",
"DataAnnotation validation failed for 'MyOptions.Nested.Children[1]' members: 'Name' with the error: 'The Name field is required.'.",
"DataAnnotation validation failed for 'MyOptions.Nested.Children[2]' members: 'Name' with the error: 'The Name field is required.'.",
},
result2.Failures);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TestValidationWithCyclicReferences()
{
NestedOptions nestedOptions = new()
{
Tall = 10,
Id = "2",
};
MyOptions options = new()
{
Name = "T",
Phone = "P",
Age = 30,
Nested = nestedOptions,
};
nestedOptions.NestedList = new()
{
new NestedOptions() { Tall = 5, Id = "1" },
nestedOptions, // Circular reference
new NestedOptions() { Tall = 7, Id = "3" },
nestedOptions // Circular reference
};
MySourceGenOptionsValidator sourceGenOptionsValidator = new();
DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
Assert.True(result1.Succeeded);
ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
Assert.True(result1.Succeeded);
}
}
public class MyOptions
{
[Range(0, 100)]
public int Age { get; set; } = 200;
[Required]
public string? Name { get; set; }
[Required]
public string? Phone { get; set; }
[ValidateObjectMembers]
public NestedOptions Nested { get; set; }
}
public class NestedOptions
{
[Range(0, 10)]
public double Tall { get; set; }
[Required]
public string? Id { get; set; }
[ValidateEnumeratedItems]
public List<ChildOptions>? Children { get; set; }
#pragma warning disable SYSLIB1211 // Source gen does static analysis for circular reference. We need to disable it for this test.
[ValidateEnumeratedItems]
public List<NestedOptions> NestedList { get; set; } // To check cycling reference
#pragma warning restore SYSLIB1211
}
public class ChildOptions
{
[Required]
public string? Name { get; set; }
}
public struct MyOptionsStruct
{
[Range(0, 100)]
public int Age { get; set; }
[Required]
public string? Name { get; set; }
[ValidateObjectMembers]
public NestedOptions Nested { get; set; }
}
[OptionsValidator]
public partial class MySourceGenOptionsValidator : IValidateOptions<MyOptions>
{
}
}
\ No newline at end of file

// <auto-generated/>
#nullable enable
#pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
......@@ -391,110 +391,6 @@ partial struct SecondValidator
}
}
}
namespace Fields
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
internal sealed partial class __ThirdModelValidator__
{
/// <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::Fields.ThirdModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P5";
context.DisplayName = baseName + "P5";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
return builder.Build();
}
}
}
namespace Fields
{
partial struct FirstValidator
{
/// <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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P1";
context.DisplayName = baseName + "P1";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
}
builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
return builder.Build();
}
}
}
namespace Fields
{
partial struct SecondValidator
{
/// <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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P4";
context.DisplayName = baseName + "P4";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
return builder.Build();
}
}
}
namespace FileScopedNamespace
{
partial struct FirstValidator
......@@ -658,7 +554,7 @@ partial struct MultiValidator
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
}
return builder.Build();
......@@ -793,14 +689,14 @@ partial struct FirstValidator
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
}
builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
if (options.P4 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P4", options.P4));
}
return builder.Build();
......@@ -1015,12 +911,12 @@ namespace RecordTypes
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P2", options.P2));
}
if (options.P3 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P3", options.P3));
}
builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
......@@ -2065,16 +1961,14 @@ namespace __OptionValidationStaticInstances
internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
internal static readonly global::MultiModelValidator.MultiValidator V3 = new global::MultiModelValidator.MultiValidator();
internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
internal static readonly global::Nested.Container2.Container3.SecondValidator V4 = new global::Nested.Container2.Container3.SecondValidator();
internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
internal static readonly global::Nested.Container4.Container5.ThirdValidator V5 = new global::Nested.Container4.Container5.ThirdValidator();
internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
internal static readonly global::RecordTypes.SecondValidator V6 = new global::RecordTypes.SecondValidator();
internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
}
}

// <auto-generated/>
#nullable enable
#pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
......@@ -391,110 +391,6 @@ partial struct SecondValidator
}
}
}
namespace Fields
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
internal sealed partial class __ThirdModelValidator__
{
/// <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::Fields.ThirdModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P5";
context.DisplayName = baseName + "P5";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
return builder.Build();
}
}
}
namespace Fields
{
partial struct FirstValidator
{
/// <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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P1";
context.DisplayName = baseName + "P1";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
}
builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
return builder.Build();
}
}
}
namespace Fields
{
partial struct SecondValidator
{
/// <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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
{
var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
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>(2);
context.MemberName = "P4";
context.DisplayName = baseName + "P4";
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
{
builder.AddResults(validationResults);
}
return builder.Build();
}
}
}
namespace FileScopedNamespace
{
partial struct FirstValidator
......@@ -658,7 +554,7 @@ partial struct MultiValidator
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
}
return builder.Build();
......@@ -793,14 +689,14 @@ partial struct FirstValidator
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
}
builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
if (options.P4 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P4", options.P4));
}
return builder.Build();
......@@ -1015,12 +911,12 @@ namespace RecordTypes
if (options.P2 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P2", options.P2));
}
if (options.P3 is not null)
{
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P3", options.P3));
}
builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
......@@ -2057,16 +1953,14 @@ namespace __OptionValidationStaticInstances
internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
internal static readonly global::MultiModelValidator.MultiValidator V3 = new global::MultiModelValidator.MultiValidator();
internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
internal static readonly global::Nested.Container2.Container3.SecondValidator V4 = new global::Nested.Container2.Container3.SecondValidator();
internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
internal static readonly global::Nested.Container4.Container5.ThirdValidator V5 = new global::Nested.Container4.Container5.ThirdValidator();
internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
internal static readonly global::RecordTypes.SecondValidator V6 = new global::RecordTypes.SecondValidator();
internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Fields;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Gen.OptionsValidation.Test;
public class FieldTests
{
[Fact]
public void Invalid()
{
var thirdModel = new ThirdModel
{
P5 = "1234",
};
var secondModel = new SecondModel
{
P4 = "1234",
};
var firstModel = new FirstModel
{
P1 = "1234",
P2 = secondModel,
P3 = thirdModel,
};
var validator = default(FirstValidator);
var vr = validator.Validate("Fields", firstModel);
Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5");
}
[Fact]
public void Valid()
{
var thirdModel = new ThirdModel
{
P5 = "12345",
P6 = 1
};
var secondModel = new SecondModel
{
P4 = "12345",
};
var firstModel = new FirstModel
{
P1 = "12345",
P2 = secondModel,
P3 = thirdModel,
};
var validator = default(FirstValidator);
Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Fields", firstModel));
}
}
......@@ -16,46 +16,46 @@ namespace Enumeration
public class FirstModel
{
[ValidateEnumeratedItems]
public IList<SecondModel>? P1;
public IList<SecondModel>? P1 { get; set; }
[ValidateEnumeratedItems(typeof(SecondValidator))]
public IList<SecondModel>? P2;
public IList<SecondModel>? P2 { get; set; }
[ValidateEnumeratedItems]
public IList<SecondModel?>? P3;
public IList<SecondModel?>? P3 { get; set; }
[ValidateEnumeratedItems]
public IList<ThirdModel>? P4;
public IList<ThirdModel>? P4 { get; set; }
[ValidateEnumeratedItems]
public IList<ThirdModel?>? P5;
public IList<ThirdModel?>? P5 { get; set; }
[ValidateEnumeratedItems]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
public IList<Nullable<ThirdModel>>? P51;
public IList<Nullable<ThirdModel>>? P51 { get; set; }
[ValidateEnumeratedItems]
public SynteticEnumerable? P6;
public SynteticEnumerable? P6 { get; set; }
[ValidateEnumeratedItems]
public SynteticEnumerable P7;
public SynteticEnumerable P7 { get; set; }
[ValidateEnumeratedItems]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
public Nullable<SynteticEnumerable> P8;
public Nullable<SynteticEnumerable> P8 { get; set; }
}
public class SecondModel
{
[Required]
[MinLength(5)]
public string P6 = string.Empty;
public string P6 { get; set; } = string.Empty;
}
public struct ThirdModel
{
[Range(0, 10)]
public int Value;
public int Value { get; set; }
}
public struct SynteticEnumerable : IEnumerable<SecondModel>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Options;
namespace Fields
{
#pragma warning disable SA1649
#pragma warning disable SA1402
#pragma warning disable S1186
#pragma warning disable CA1822
public class FirstModel
{
[Required]
[MinLength(5)]
public string P1 = string.Empty;
[Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))]
public SecondModel? P2;
[Microsoft.Extensions.Options.ValidateObjectMembers]
public ThirdModel P3;
}
public class SecondModel
{
[Required]
[MinLength(5)]
public string P4 = string.Empty;
}
public struct ThirdModel
{
[Required]
[MinLength(5)]
public string P5 = string.Empty;
public int P6 = default;
public ThirdModel(object _)
{
}
}
[OptionsValidator]
public partial struct FirstValidator : IValidateOptions<FirstModel>
{
public void Validate()
{
}
public void Validate(int _)
{
}
public void Validate(string? _)
{
}
public void Validate(string? _0, object _1)
{
}
}
[OptionsValidator]
public partial struct SecondValidator : IValidateOptions<SecondModel>
{
}
}
......@@ -12,7 +12,7 @@ public class FirstModel
{
[Required]
[MinLength(5)]
public string P1 = string.Empty;
public string P1 { get; set; } = string.Empty;
}
[OptionsValidator]
......
......@@ -154,7 +154,7 @@ public class DerivedModel : RequiredAttributeModel
public string? DerivedVal { get; set; }
[Required]
internal virtual int? VirtualValWithAttr { get; set; }
public virtual int? VirtualValWithAttr { get; set; }
public virtual int? VirtualValWithoutAttr { get; set; }
......@@ -164,7 +164,7 @@ public class DerivedModel : RequiredAttributeModel
public class LeafModel : DerivedModel
{
internal override int? VirtualValWithAttr { get; set; }
public override int? VirtualValWithAttr { get; set; }
[Required]
public override int? VirtualValWithoutAttr { get; set; }
......
......@@ -13,17 +13,17 @@ public class FirstModel
{
[Required]
[MinLength(5)]
public string P1 = string.Empty;
public string P1 { get; set; } = string.Empty;
[Microsoft.Extensions.Options.ValidateObjectMembers(typeof(MultiValidator))]
public SecondModel? P2;
public SecondModel? P2 { get; set; }
}
public class SecondModel
{
[Required]
[MinLength(5)]
public string P3 = string.Empty;
public string P3 { get; set; } = string.Empty;
}
[OptionsValidator]
......
......@@ -36,7 +36,7 @@ public class ThirdModel
{
[Required]
[MinLength(5)]
public string? P5;
public string? P5 { get; set; }
}
[OptionsValidator]
......
......@@ -13,7 +13,7 @@ namespace SelfValidation
public class FirstModel : IValidatableObject
{
[Required]
public string P1 = string.Empty;
public string P1 { get; set; } = string.Empty;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册