Skip to content

Improve non-DI usage of built-in feature filters #378

@zhiyuanliang-ms

Description

@zhiyuanliang-ms

After Microsoft.FeatureManagement 3.1.0, we exposed FeatureManager and ConfigurationFeatureDefintionProvider classes to public and the non-DI usage of Feature Management is supported:

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var featureDefintionProvider = new ConfigurationFeatureDefinitionProvider(configuration);

var featureManager = new FeatureManager(featureDefintionProvider)();

bool res = await featureManager.IsEnabledAsync("MyFeature");

However, the built-in feature filters are designed for DI usage originally. If users want to use feature filters in non-DI Feature Management. The usage will be like:

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var featureDefintionProvider = new ConfigurationFeatureDefinitionProvider(configuration);

ILoggerFactory loggerFactory = new LoggerFactory();

var targetingFilter = new ContextualTargetingFilter(Options.Create(new TargetingEvaluationOptions()), loggerFactory);

var timeWindowFilter = new TimeWindowFilter(loggerFactory);

var featureManager = new FeatureManager(featureDefintionProvider)
{
    FeatureFilters = new List<IFeatureFilterMetadata>() { targetingFilter, timeWindowFilter },
    Logger = loggerFactory.CreateLogger<FeatureManager>()
};

bool res = await featureManager.IsEnabledAsync("MyFeature");

The current non-DI usage looks kind of messy.

Notice that the current constructor of built-in filters are:

public PercentageFilter(ILoggerFactory loggerFactory)
{
    _logger = loggerFactory.CreateLogger<PercentageFilter>();
}

public TimeWindowFilter(ILoggerFactory loggerFactory)
{
    _logger = loggerFactory?.CreateLogger<TimeWindowFilter>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}

public ContextualTargetingFilter(IOptions<TargetingEvaluationOptions> options, ILoggerFactory loggerFactory)
{
    _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
    _logger = loggerFactory?.CreateLogger<ContextualTargetingFilter>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}

public TargetingFilter(IOptions<TargetingEvaluationOptions> options, ITargetingContextAccessor contextAccessor, ILoggerFactory loggerFactory)
{
    _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
    _contextualFilter = new ContextualTargetingFilter(options, loggerFactory);
    _logger = loggerFactory?.CreateLogger<TargetingFilter>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}

There are two inconsistent things:

  1. Built-in feature filter constructors require an ILoggerFactory parameter which cannot be null. FeatureManager has a public ILogger property which can be null.
  2. Currently, the constructor of FeatureManager has a default value for FeatureManagementOptions Add a constructor without FeatureManagementOption parameter for FeatureManager #363. Targeting filters still require an IOptions parameter which does not have a default value.

I propose that we should at least support such non-DI usage for built-in feature filters:

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var featureDefintionProvider = new ConfigurationFeatureDefinitionProvider(configuration);

var targetingFilter = new ContextualTargetingFilter();

var timeWindowFilter = new TimeWindowFilter();

var featureManager = new FeatureManager(featureDefintionProvider)
{
    FeatureFilters = new List<IFeatureFilterMetadata>() { targetingFilter, timeWindowFilter }
};

bool res = await featureManager.IsEnabledAsync("MyFeature");

by enabling logger of built-in filters to be null and giving a default value for TargetingEvaluationOptions.

This change will make non-DI usage simpler. Another reason we decided to expose FeatureManager class is to allow third-party DI containers to use FeatureManagement. The current usage of FeatureManagement with third party DI containers can be found here. With this change, we can also make it easier for third party DI containers to use FeatureManagement.

My further thought is to make built-in feature filters have the same pattern as FeatureManager where Logger and TargetingEvaluationOptions are public properties instead of parameters of constructor.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions