Skip to content

Feature filter parameter object#589

Open
linglingye001 wants to merge 1 commit intomainfrom
linglingye/parameter-object
Open

Feature filter parameter object#589
linglingye001 wants to merge 1 commit intomainfrom
linglingye/parameter-object

Conversation

@linglingye001
Copy link
Copy Markdown
Member

@linglingye001 linglingye001 commented Apr 2, 2026

Overview

This PR introduces a new ParameterObject property to FeatureFilterEvaluationContext, enabling custom IFeatureDefinitionProvider implementations to supply feature filter settings directly without requiring IConfiguration.

Motivation

Currently, feature filters receive their configuration through IConfiguration, which works well for JSON/config file-based feature definitions. However, custom IFeatureDefinitionProvider implementations that source feature definitions from alternative backends (e.g., databases, REST APIs, gRPC services) are forced to:

  1. Construct an IConfiguration object from their native data format
  2. Have filters bind that configuration back to strongly-typed settings objects

This round-trip is unnecessary overhead when the provider already has the ability to create strongly-typed settings objects like TargetingFilterSettings or TimeWindowFilterSettings directly.

Code Examples - Before & After

Before: Using IConfiguration with InMemoryCollection

When implementing a custom IFeatureDefinitionProvider that fetches feature definitions from a database, you previously had to construct an IConfiguration object to pass filter parameters:

// Fetch from database
var dbFeature = await _database.GetFeatureAsync(featureName);

// Must convert to IConfiguration even though we already have the data
var configData = new Dictionary<string, string>
{
    ["Audience:Users:0"] = dbFeature.TargetedUsers[0],
    ["Audience:Users:1"] = dbFeature.TargetedUsers[1],
    ["Audience:Groups:0:Name"] = dbFeature.TargetedGroups[0].Name,
    ["Audience:Groups:0:RolloutPercentage"] = dbFeature.TargetedGroups[0].RolloutPercentage.ToString(),
    ["Audience:DefaultRolloutPercentage"] = dbFeature.DefaultRolloutPercentage.ToString()
};

return new FeatureDefinition
{
    Name = featureName,
    EnabledFor = new[]
    {
        new FeatureFilterConfiguration
        {
            Name = "Targeting",
            Parameters = new ConfigurationBuilder()
                .AddInMemoryCollection(configData)
                .Build()
        }
    }
};

This approach has several drawbacks:

  • Verbose dictionary key construction with magic strings
  • Error-prone index management for arrays
  • Unnecessary serialization to key-value pairs just to deserialize back to objects

After: Using ParameterObject Directly

With the new ParameterObject property, custom providers can supply strongly-typed settings directly:

// Fetch from database
var dbFeature = await _database.GetFeatureAsync(featureName);

// Directly create strongly-typed settings
var targetingSettings = new TargetingFilterSettings
{
    Audience = new TargetingFilterAudience
    {
        Users = dbFeature.TargetedUsers,
        Groups = dbFeature.TargetedGroups.Select(g => new GroupRollout
        {
            Name = g.Name,
            RolloutPercentage = g.RolloutPercentage
        }).ToList(),
        DefaultRolloutPercentage = dbFeature.DefaultRolloutPercentage
    }
};

return new FeatureDefinition
{
    Name = featureName,
    EnabledFor = new[]
    {
        new FeatureFilterConfiguration
        {
            Name = "Targeting",
            ParameterObject = targetingSettings  // Direct assignment!
        }
    }
};

Changes

  • FeatureFilterEvaluationContext: Added ParameterObject property to hold strongly-typed settings provided by custom providers
  • Built-in filters updated (PercentageFilter, TimeWindowFilter, ContextualTargetingFilter): Now check ParameterObject first before falling back to Settings or binding from Parameters

Precedence Order

When evaluating filter settings:

  1. ParameterObject (if set) — Direct strongly-typed object from provider
  2. Settings — Pre-bound settings via IFilterParametersBinder
  3. Parameters — Bind from IConfiguration

The expectation is that systems using ParameterObject will not set Parameters/IConfiguration.

/// instead of constructing an <see cref="IConfiguration"/> instance.
/// When set, feature filters should prefer this over <see cref="Parameters"/>.
/// </summary>
public object ParameterObject { get; set; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public object ParameterObject { get; set; }
public object ParametersObject { get; set; }

public IConfiguration Parameters { get; set; }

/// <summary>
/// A strongly-typed parameter object, if any, provided by a custom <see cref="IFeatureDefinitionProvider"/>.
Copy link
Copy Markdown
Member

@jimmyca15 jimmyca15 Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A strongly-typed parameter object, if any, provided by a custom <see cref="IFeatureDefinitionProvider"/>.
/// The settings provided for the feature filter to use when evaluating whether the feature should be enabled. This property takes precedence over Parameters if both are provided.

TargetingFilterSettings settings = (TargetingFilterSettings)context.Settings ?? (TargetingFilterSettings)BindParameters(context.Parameters);
// Check if ParameterObject available (takes precedence), then prebound settings, otherwise bind from parameters.
TargetingFilterSettings settings = context.ParameterObject != null
? (TargetingFilterSettings)context.ParameterObject
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the exception that will be raised by the system if someone provides the incorrect type here. Ideally we'd like a FeatureManagementException if someone populates ParametersObject, but it's not TargetingFilterSettings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants