Skip to content

[API Proposal]: Add ConfigurationIgnoreAttribute to exclude properties from configuration binding #125111

@ondrejtucny

Description

@ondrejtucny

Background and motivation

Currently, Microsoft.Extensions.Configuration provides ConfigurationKeyNameAttribute to customize the configuration key mapped to a property (see #36010), but there is no corresponding attribute to exclude a property from binding altogether. This is a clear gap in the API surface.

Common scenarios where excluding a property is necessary:

  • Backwards compatibility — a public property exists on the options type for legacy reasons but should not participate in binding.
  • Post-load value conversion — the raw bound value needs additional processing before being exposed through a different property.
  • Post-load options derivatives — a property is computed or assembled from other bound values after configuration is loaded.

As a concrete example, consider an options class that needs to bind values from an IConfigurationSection into a pre-created object, followed by post-processing and default selection. Today, the only workaround is to abuse ConfigurationKeyName with an intentionally invalid key to prevent binding, which is fragile and unclear:

[ConfigurationKeyName("__ignored_" + nameof(DefaultFormat))] // Hack: no ignore attribute exists
public CsvFormatOptions DefaultFormat { get; set; }

Users may also reasonably expect attributes like [JsonIgnore] or interfaces like IJsonOnDeserialized to influence the configuration binder, but they do not. A dedicated, first-class attribute would make intent explicit and the behavior discoverable.

Related issue: #58384

API Proposal

namespace Microsoft.Extensions.Configuration;

/// <summary>
/// Specifies that a property should be excluded from configuration binding.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ConfigurationIgnoreAttribute : Attribute
{
}

API Usage

/// <summary>
/// CSV ingestion options describing various CSV formats ingested by the system.
/// </summary>
public class CsvIngestionOptions
{
    /// <summary>
    /// Processed CSV format options. Excluded from binding because it is
    /// computed from <see cref="DefaultFormatSection"/> during post-load processing.
    /// </summary>
    [ConfigurationIgnore]
    public CsvFormatOptions DefaultFormat { get; set; }

    /// <summary>
    /// Raw configuration section bound by the configuration system.
    /// </summary>
    [ConfigurationKeyName(nameof(DefaultFormat))]
    public IConfigurationSection? DefaultFormatSection { get; set; }

    /// <summary>
    /// Invoked after configuration is loaded to apply post-processing and defaults.
    /// </summary>
    internal void OnConfigurationLoaded()
    {
        DefaultFormat = new CsvFormatOptions();
        DefaultFormatSection?.Bind(DefaultFormat);
        DefaultFormat.EnsureEncodingDefined();
    }
}

Another example — a property with a public getter and setter that exists for programmatic use but should not be populated by the binder:

public class FeatureFlags
{
    public string RawFlags { get; set; }

    /// <summary>
    /// Parsed representation of <see cref="RawFlags"/>. Populated during post-load
    /// processing; should not be bound from configuration directly.
    /// </summary>
    [ConfigurationIgnore]
    public IReadOnlyDictionary<string, bool> ParsedFlags { get; set; } = new Dictionary<string, bool>();
}

Note: as of .NET 9, the binder already skips read-only properties (getter-only / init-only without a corresponding section). The attribute proposed here targets writable properties that can be bound but should not be.

Additionally, properties with non-public setters (e.g. public CsvFormatOptions DefaultFormat { get; private set; }) should be ignored by the binder as well, if they are not already. The attribute would serve as an explicit opt-out for the remaining cases where the property has a fully public getter and setter but must still be excluded from binding.

Alternative Designs

  1. Honor [JsonIgnore] — The binder could recognize System.Text.Json.Serialization.JsonIgnoreAttribute. This would feel natural to users already familiar with JSON serialization attributes, but it creates an undesirable coupling between the configuration system and the JSON serializer. It would also not cover scenarios where a property should be serialized to JSON but excluded from configuration binding (or vice versa).

  2. Convention-based exclusion via BinderOptions — Instead of (or in addition to) an attribute, a predicate could be exposed on BinderOptions to filter properties programmatically:

    section.Bind(options, o => o.ShouldBindProperty = prop => prop.Name != "DefaultFormat");

    This is more flexible but less discoverable and more error-prone than a declarative attribute.

  3. Do nothing, document the workaround — Continue relying on ConfigurationKeyName with an invalid key as a workaround. This is fragile, non-obvious, pollutes the options type with misleading metadata, and potentially creates a new configuration attack surface.

Risks

  • Minimal breaking-change risk — This is a purely additive API. Existing code is unaffected.
  • Source generator support — The configuration binding source generator (#44493) would need to be updated to recognize and respect this attribute. This should be straightforward since it already handles ConfigurationKeyNameAttribute.
  • Discoverability with existing JSON attributes — Users may still try [JsonIgnore] first and be surprised it doesn't work. The documentation should explicitly note that JSON serialization attributes are not honored by the configuration binder, and point to ConfigurationIgnoreAttribute instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-Extensions-ConfigurationuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions