Using the FeatureManagement
feature in .NET Core 3.1, I am trying to write a custom database feature provider that will pull features and whether or not they are enabled from a SQL Server database. To do this, the Microsoft documentation says you need to implement the IFeatureDefinitionProvider
interface.
You need to return a FeatureDefinition
class which doesn't contain whether or not the feature is enabled but contains IEnumerable<FeatureFilterConfiguration>
. There are no examples anywhere online as the feature is pretty new, and even looking at the Azure implementation (one of their two suggested implementations along with appsettings.json which will not work for this specific use case) is pretty confusing. The documentation for the FeatureDefinition
class and EnabledFor do not provide any useful information.
Does anyone know how to use Microsoft.FeatureManagement
in .NET Core 3.1 to extract feature data from the database?
To add a new feature to an existing instance of SQL Server, select Installation in the left-hand navigation area, and then select New SQL Server stand-alone installation or add features to an existing installation. The System Configuration Checker will run a discovery operation on your computer.
You are right, the examples and tutorials are unbelievably shallow, confusing and don't clarify any of the concepts. However I couldn't help but notice that the example suggested above only deals with configuration settings, JSON, etc. but it would be hard to use for your purpose, i.e., querying SQL Server and returning the state of the feature. What you need is an example of how to implement the interface IFeatureDefinitionProvider
and be able to query the SQL Server database in a flexible way. This is the way I would do it and not necessarily using the Entity Framework, ADO.NET is enough:
namespace FeatureDefinitionProviderDemo
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.FeatureManagement;
public class FeatureDefinitionProvider : IFeatureDefinitionProvider
{
private const string FirstFeatureName = "FirstFeature";
private const string SecondFeatureName = "SecondFeature";
private const string ThirdFeatureName = "ThirdFeature";
public Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
{
// YOU SUPPOSEDLY GO ASYNCHRONOUSLY TO THE DATABASE AND TAKE THE GIVEN FEATURE PROPERTIES
// NOTE: part of the following is dummy
var featureDefinition = featureName switch // NOTE: I'm using new C# switch expression here
{
// let's say the feature is boolean and it is enabled
FirstFeatureName => CreateEnabledFeatureDefinition(featureName),
// let's say the feature is boolean and it is disabled
SecondFeatureName => CreateDisabledFeatureDefinition(featureName),
// let's say this one is a 50% percentage
ThirdFeatureName => CreatePercentageFeatureDefinition(featureName, 50),
_ => throw new NotSupportedException("The requested feature is not supported.")
};
return Task.FromResult(featureDefinition);
}
public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
{
foreach (var featureDefinition in new[]
{
await GetFeatureDefinitionAsync(FirstFeatureName),
await GetFeatureDefinitionAsync(SecondFeatureName),
await GetFeatureDefinitionAsync(ThirdFeatureName),
})
{
yield return featureDefinition;
}
}
private FeatureDefinition CreateEnabledFeatureDefinition(string featureName)
{
// NOTE: adding a filter configuration without configurations means enabled
return new FeatureDefinition
{
Name = featureName,
EnabledFor = new[]
{
new FeatureFilterConfiguration
{
Name = "AlwaysOn"
}
}
};
}
private FeatureDefinition CreateDisabledFeatureDefinition(string featureName)
{
// NOTE: don't add any filter configuration as by default it is disabled
return new FeatureDefinition
{
Name = featureName
};
}
private FeatureDefinition CreatePercentageFeatureDefinition(string featureName, double percentage)
{
// NOTE: this one is a bit more complicated and could be connected to a percentage SQL server setting
return new FeatureDefinition
{
Name = featureName,
EnabledFor = new[]
{
new FeatureFilterConfiguration
{
Name = "Percentage",
Parameters = new DoubleConfiguration(percentage)
}
}
};
}
}
}
namespace FeatureDefinitionProviderDemo
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
internal class DoubleConfiguration : IConfiguration
{
private readonly double _percentage;
public DoubleConfiguration(double percentage)
{
_percentage = percentage;
}
public IEnumerable<IConfigurationSection> GetChildren()
{
// NOTE: no children
return Enumerable.Empty<IConfigurationSection>();
}
public IChangeToken GetReloadToken()
{
// NOTE: this is not supported and not consumed either
throw new NotSupportedException();
}
public IConfigurationSection GetSection(string key)
{
// NOTE: this is not supported and not consumed either
throw new NotSupportedException();
}
public string this[string key]
{
get => _percentage.ToString("F"); // this produces the requested value
// NOTE: this is not supported and not consumed either
set => throw new NotSupportedException();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With