// 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
{
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;
}
}
}
</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" />
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;
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)
{
memberType = field.Type;
break;
+ */
default:
// we only care about properties and fields
return null;
public class SecondModel
{
[Required]
- public string? P3;
+ public string? P3 { get; set; }
}
[OptionsValidator]
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; }
public class SecondModel
{
[Required]
- public string? P3;
+ public string? P3 { get; set; }
}
[OptionsValidator]
}
");
- 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));
}
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]
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]
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]
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]
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]
public class FirstModel
{
[Required]
- public string P1;
+ public string P1 { get; set; }
}
[OptionsValidator]
{
[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]
{
[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]
{
[Required]
[ValidateObjectMembers]
- public SecondModel? P1;
+ public SecondModel? P1 { get; set; }
}
public class SecondModel
{
- public string P2;
+ public string P2 { get; set; };
}
[OptionsValidator]
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]
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]
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]
{
[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]
{
[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]
{
[Required]
[ValidateEnumeratedItems]
- public int P1;
+ public int P1 { get; set; }
}
[OptionsValidator]
<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>
--- /dev/null
+// 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
}
}
}
-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
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();
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();
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));
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
}
}
}
-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
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();
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();
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));
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();
}
}
+++ /dev/null
-// 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));
- }
-}
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>
+++ /dev/null
-// 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>
- {
- }
-}
{
[Required]
[MinLength(5)]
- public string P1 = string.Empty;
+ public string P1 { get; set; } = string.Empty;
}
[OptionsValidator]
public string? DerivedVal { get; set; }
[Required]
- internal virtual int? VirtualValWithAttr { get; set; }
+ public virtual int? VirtualValWithAttr { get; set; }
public virtual int? VirtualValWithoutAttr { get; set; }
public class LeafModel : DerivedModel
{
- internal override int? VirtualValWithAttr { get; set; }
+ public override int? VirtualValWithAttr { get; set; }
[Required]
public override int? VirtualValWithoutAttr { get; set; }
{
[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]
{
[Required]
[MinLength(5)]
- public string? P5;
+ public string? P5 { get; set; }
}
[OptionsValidator]
public class FirstModel : IValidatableObject
{
[Required]
- public string P1 = string.Empty;
+ public string P1 { get; set; } = string.Empty;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{