Skip to content

Generated option validators do not check the contents of dictionaries. #115601

Open
@aetos382

Description

@aetos382

Description

Generated option validators do not check the contents of Dictionary<K, V> types.

Reproduction Steps

using System.ComponentModel.DataAnnotations;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var appBuilder = Host.CreateApplicationBuilder(args);

var configuration = appBuilder.Configuration;

configuration["AppOptions:ComplexOptions:Foo:Value"] = "1";
configuration["AppOptions:ComplexOptions:Bar:Value"] = "200";

var services = appBuilder.Services;

services
    .AddOptionsWithValidateOnStart<AppOptions, AppOptionsValidator>()
    .BindConfiguration("AppOptions");

using var host = appBuilder.Build();

await host.StartAsync().ConfigureAwait(false);

var appOptions = host.Services.GetRequiredService<IOptions<AppOptions>>().Value;

foreach (var (key, value) in appOptions.ComplexOptions)
{
    Console.WriteLine($"{key}={value.Value}");
}

await host.StopAsync().ConfigureAwait(false);

class AppOptions
{
    [Required]
    [ValidateObjectMembers]
    [ValidateEnumeratedItems]
    public Dictionary<string, NestedOptions> ComplexOptions { get; set; } = [];
}

class NestedOptions
{
    [Range(1, 100)]
    public int Value { get; set; }
}

[OptionsValidator]
partial class AppOptionsValidator :
    IValidateOptions<AppOptions>
{
}

Expected behavior

A validation error occurs for AppOptions:ComplexOptions:Bar:Value.

Actual behavior

No validation errors occurred.

Regression?

No response

Known Workarounds

class AppOptions
{
    [Required]
-   [ValidateObjectMembers]
-   [ValidateEnumeratedItems]
    public Dictionary<string, NestedOptions> ComplexOptions { get; set; } = [];

+   [ValidateEnumeratedItems]
+   public ICollection<NestedOptions> ComplexOptionValues => ComplexOptions.Values;
}

Configuration

project:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
  </ItemGroup>

</Project>
dotnet --info PS> dotnet --info

.NET SDK:
Version: 9.0.300
Commit: 15606fe0a8
Workload version: 9.0.300-manifests.87b8cca8
MSBuild version: 17.14.5+edd3bbf37

Runtime Environment:
OS Name: Windows
OS Version: 10.0.26100
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.300\

.NET workloads installed:
[android]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 35.0.61/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.android\35.0.61\WorkloadManifest.json
Install Type: Msi

[aspire]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 8.2.2/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.2.2\WorkloadManifest.json
Install Type: Msi

[ios]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 18.4.9289/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.ios\18.4.9289\WorkloadManifest.json
Install Type: Msi

[maccatalyst]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 18.4.9289/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.maccatalyst\18.4.9289\WorkloadManifest.json
Install Type: Msi

[maui-windows]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 9.0.51/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.maui\9.0.51\WorkloadManifest.json
Install Type: Msi

[wasm-tools]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 9.0.5/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.workload.mono.toolchain.current\9.0.5\WorkloadManifest.json
Install Type: Msi

[wasm-tools-net8]
Installation Source: SDK 9.0.300, VS 17.14.36109.1
Manifest Version: 9.0.5/9.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.workload.mono.toolchain.net8\9.0.5\WorkloadManifest.json
Install Type: Msi

Configured to use loose manifests when installing new manifests.

Host:
Version: 9.0.5
Architecture: x64
Commit: e36e4d1

.NET SDKs installed:
9.0.300 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions