Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Options Duplicated ListItem #109015

Open
giammin opened this issue Oct 18, 2024 · 3 comments
Open

Options Duplicated ListItem #109015

giammin opened this issue Oct 18, 2024 · 3 comments
Labels
needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners untriaged New issue has not been triaged by the area owner

Comments

@giammin
Copy link

giammin commented Oct 18, 2024

Description

I'm experiencing a strange behavior when using IOption to inject a configuration object deserialized from a config file.

Items on a list are duplicated.

Reproduction Steps

example repo: https://github.com/giammin/OptionDuplicatedListItem

program.cs

using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

var apiSection = builder.Configuration.GetSection(ApiConfig.SectionName);
builder.Services.AddOptions<ApiConfig>().Bind(apiSection);

var app = builder.Build();

app.MapGet("/config", (IOptionsSnapshot<ApiConfig> config) => TypedResults.Ok(new { config.Value.Pricing }));

app.Run();

public record ApiConfig
{
    public const string SectionName = "ApiConfig";
    public required Pricing Pricing { get; init; } = null!;
}

public record Pricing(Dictionary<ProjectCategory, IEnumerable<ProjectPricingPlan>> Projects);

public record ProjectPricingPlan(string Name, int Limit, decimal Price);

public enum ProjectCategory { CatA, CatB, }

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiConfig": {
    "Pricing": {
      "Projects": {
        "CatA": [
          {
            "Name": "Basic",
            "Limit": 100,
            "Price": 8.75
          },
          {
            "Name": "Large",
            "Limit": 500,
            "Price": 19.5
          }
        ],
        "CatB": [
          {
            "Name": "Basic",
            "Limit": 100,
            "Price": 280
          },
          {
            "Name": "Large",
            "Limit": 250,
            "Price": 480
          }
        ]
      }
    }
  }
}

Expected behavior

{
  "pricing": {
    "projects": {
      "CatA": [
        {
          "name": "Basic",
          "limit": 100,
          "price": 8.75
        },
        {
          "name": "Large",
          "limit": 500,
          "price": 19.5
        }
      ],
      "CatB": [
        {
          "name": "Basic",
          "limit": 100,
          "price": 280
        },
        {
          "name": "Large",
          "limit": 250,
          "price": 480
        }
      ]
    }
  }
}

Actual behavior

{
  "pricing": {
    "projects": {
      "CatA": [
        {
          "name": "Basic",
          "limit": 100,
          "price": 8.75
        },
        {
          "name": "Large",
          "limit": 500,
          "price": 19.5
        },
        {
          "name": "Basic",
          "limit": 100,
          "price": 8.75
        },
        {
          "name": "Large",
          "limit": 500,
          "price": 19.5
        }
      ],
      "CatB": [
        {
          "name": "Basic",
          "limit": 100,
          "price": 280
        },
        {
          "name": "Large",
          "limit": 250,
          "price": 480
        },
        {
          "name": "Basic",
          "limit": 100,
          "price": 280
        },
        {
          "name": "Large",
          "limit": 250,
          "price": 480
        }
      ]
    }
  }
}

Regression?

No response

Known Workarounds

No response

Configuration

pwsh  dotnet --info
.NET SDK:
Version: 8.0.403
Commit: c64aa40a71
Workload version: 8.0.400-manifests.58db758f
MSBuild version: 17.11.9+a69bbaaf5

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

.NET workloads installed:
Configured to use loose manifests when installing new manifests.
[aspire]
Installation Source: SDK 8.0.400, VS 17.11.35327.3
Manifest Version: 8.2.1/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.2.1\WorkloadManifest.json
Install Type: Msi

Host:
Version: 8.0.10
Architecture: x64
Commit: 81cabf2

.NET SDKs installed:
6.0.427 [C:\Program Files\dotnet\sdk]
8.0.110 [C:\Program Files\dotnet\sdk]
8.0.403 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.35 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.35 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.35 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.10 [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

Other information

it is not related to json deserialization. It happens only when using ioptions

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 18, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Oct 18, 2024
@julealgon
Copy link

Does it make a difference if you replace this:

public required Pricing Pricing { get; init; } = null!;

With this:

public Pricing Pricing { get; } = new();

After changing your Pricing model to have a readonly dictionary reference?

public record Pricing
{
    public Dictionary<ProjectCategory, IEnumerable<ProjectPricingPlan>> Projects { get; } = new();
}

Not necessarily a fix, but I suspect it might be initialing/accumulating the values twice because of the writable property plus the constructor parameter.

The above could be a workaround and also help with the investigation.

@giammin
Copy link
Author

giammin commented Oct 18, 2024

actually simply initializing the property does the trick

public required Pricing Pricing { get; init; } = new([]);

thanks for the hint!

@julealgon
Copy link

actually simply initializing the property does the trick

public required Pricing Pricing { get; init; } = new([]);

thanks for the hint!

@giammin if you want to keep this version, notice you can now also remove the init and the required, for added simplicity and robustness:

public Pricing Pricing { get; } = new([]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

2 participants