Skip to content

Feature filter parameter object#589

Merged
linglingye001 merged 4 commits intomainfrom
linglingye/parameter-object
Apr 16, 2026
Merged

Feature filter parameter object#589
linglingye001 merged 4 commits 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.

Comment thread src/Microsoft.FeatureManagement/FeatureFilterConfiguration.cs Outdated
Comment thread src/Microsoft.FeatureManagement/FeatureFilterEvaluationContext.cs Outdated
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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Got it. Added FeatureManagementError.InvalidParametersObject for this scenario.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Discussed offline, throw ArgumentException for incorrect type.

Comment thread src/Microsoft.FeatureManagement/FeatureFilterEvaluationContext.cs Outdated
Comment thread src/Microsoft.FeatureManagement/FeatureManagementError.cs Outdated
Comment thread src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs Fixed
Comment thread src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs Fixed
@linglingye001 linglingye001 mentioned this pull request Apr 15, 2026
Comment thread src/Microsoft.FeatureManagement/FeatureFilterEvaluationContext.cs Outdated
@jimmyca15
Copy link
Copy Markdown
Member

Copilot's findings of "This assignment to parametersObject is useless, since its value is never read." are valid and should be addressed before merging.

@linglingye001 linglingye001 merged commit dabb8a4 into main Apr 16, 2026
5 checks passed
{
FeatureName = featureDefinition.Name,
Parameters = featureFilterConfiguration.Parameters,
ParametersObject = featureFilterConfiguration.ParametersObject,
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.

Can we set ParametersObject to FeatureFilterEvaluationContext.Settings instead of adding a new ParametersObject property.

@jimmyca15

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.

I think that's a good idea. We'll need an update on the description of Settings. Something like

/// <summary>
/// A settings object, if any, provided for the feature filter to use when evaluating whether the feature should be enabled.
/// This property is populated in two cases:
/// For features that provide parameters as an object.
/// For <see cref="IFeatureFilter"/>s that implement <see cref="IFilterParametersBinder"/>.
/// </summary>

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.


//
// Check if prebound settings available, otherwise bind from parameters.
TimeWindowFilterSettings settings = (TimeWindowFilterSettings)context.Settings ?? (TimeWindowFilterSettings)BindParameters(context.Parameters);
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.

If we set the new parametersObject to context.settings, the code path will be more unified.

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.

3 participants