Skip to content

feat(variants): allow in-memory objects - #615

Open
Степан (Stepami) wants to merge 7 commits into
microsoft:mainfrom
Stepami:feature/variants-params
Open

feat(variants): allow in-memory objects#615
Степан (Stepami) wants to merge 7 commits into
microsoft:mainfrom
Stepami:feature/variants-params

Conversation

@Stepami

Copy link
Copy Markdown

Why this PR?

Implementing IFeatureDefinitionProvider makes it inconvinient to fill variant bound values if they don't come from configuration.

Similarly to feature filters I decided to introduce an object that can be used as an alternative ConfigurationValue. Custom IFeatureDefinitionProvider implementations can populate this property directly instead of constructing an IConfigurationSection instance.

Visible Changes

  • Microsoft.FeatureManagement.VariantDefinition.ConfigurationObject
  • Microsoft.FeatureManagement.Variant.ConfigurationObject

@Stepami

Copy link
Copy Markdown
Author

Hey, Zhiyuan Liang (@zhiyuanliang-ms)! could you take a look please?

@zhiyuanliang-ms

Copy link
Copy Markdown
Member

My understanding is that this enhancement is intended specifically for the custom feature definition provider scenario. Is that correct?

@Stepami

Степан (Stepami) commented Aug 26, 2026

Copy link
Copy Markdown
Author

My understanding is that this enhancement is intended specifically for the custom feature definition provider scenario. Is that correct?

yes it is. right now i am implementing a custom provider. i need to do something like this in order to put data from an external storage into the definition:

new VariantDefinition
{
    Name = "...",
    ConfigurationValue =
        new ConfigurationBuilder()
            .AddInMemoryCollection([new("Value", variantValue.ToString())])
            .Build()
            .GetSection("Value")
}

@Stepami

Copy link
Copy Markdown
Author

And the idea came to me from the FeatureFilterConfiguration class

@zhiyuanliang-ms

Copy link
Copy Markdown
Member

Hey, Степан (@Stepami)

We discussed this scenario internally, and we agree that supporting in-memory variant values is useful for custom IFeatureDefinitionProvider implementations.

One concern we have is that: for customers using the built-in ConfigurationFeatureDefinitionProvider, which we expect to be the common case, this property would not be populated and therefore would not provide any additional value.

Before adding a new public property, we would like to think through what the story should be for those customers as well. Ideally, this should feel like a generally useful part of the variant configuration model, rather than an API surface that only applies when a custom provider is used.

For example, one question we are considering is whether the built-in provider should also be able to populate an object representation from the configured variant value, or whether there is another API shape that gives both built-in and custom provider users a consistent way to consume variant configuration.

@zhiyuanliang-ms

Copy link
Copy Markdown
Member

I have been thinking about the following options:

  1. Have ConfigurationFeatureDefinitionProvider populate ConfigurationObject as well, for example by constructing a JsonNode from the IConfigurationSection.

I used to think this is the best option. But after a second look, I realize the conversion will not be lossless. For example, a numeric value could end up being reconstructed as a string.

  1. Today, Variant.Configuration is an IConfigurationSection, so customers using ConfigurationFeatureDefinitionProvider can naturally use the existing configuration binding APIs, such as Bind or Get<T>(), to convert the variant value into a typed object.

For example

Variant variant = await featureManager.GetVariantAsync("MyFeature");
MySettings settings = variant.Configuration.Get<MySettings>();

I am thinking about adding a new extension method to Variant. Something like:

Variant variant = await featureManager.GetVariantAsync("MyFeature");
MySettings settings = variant.GetConfiguration<MySettings>();

Or we can even add a new extension method to IVariantFeatureManager
like

MySettings settings = await featureManager.GetVariantConfigurationAsync<MySettings>("MyFeature");

This can unify the story that people who uses custom feature definition provider with in-memory object and built-in configuration feature defition provider.

@Stepami

Copy link
Copy Markdown
Author

hey Zhiyuan Liang (@zhiyuanliang-ms) !

I agree that ConfigurationFeatureDefinitionProvider could populate this property.

I'll take a few days to think this through

@Stepami

Copy link
Copy Markdown
Author

hey Zhiyuan Liang (@zhiyuanliang-ms)

I decided to do both populate the data supplied by the buil-in configuration provider and create the common API used to consume variant configuration through extension method.

ConfigurationObject

Now ConfigurationObject is filled with a read-only dictionary of relative configuration paths and string values inside the ConfigurationFeatureDefinitionProvider. I found this solution balanced as we don't lose the data as in JSON reconstruction and preserve original configuration structure. Relative paths preserve nested settings and array indices, while the empty key represents an inline scalar value. Case-insensitive lookup follows configuration conventions, and the read-only wrapper prevents consumers from modifying the shared dictionary.

GetConfiguration

Variant.GetConfiguration<T>() provides a common consumption API:

  • If ConfigurationObject is assignable to T, it returns that object directly. Custom providers can supply typed settings without serialization or binding, and reference identity is preserved.
  • Otherwise, it delegates to Configuration.Get<T>(). Built-in-provider consumers can request typed settings without handling the dictionary themselves. An incompatible object does not prevent using the existing configuration representation.
  • If the variant or fallback configuration is absent, it returns default(T). Callers can request nullable value types, such as int?, when they need to distinguish absence from zero.

T is unconstrained because variant values can be both complex objects and inline scalars. Pattern matching supports reference types and boxed value types.

I placed the method on Variant as an extension so callers can consume an already evaluated variant and retain access to its name, without expanding IVariantFeatureManager or changing custom implementations.

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