DEV Community

Ahmet Küçükoğlu
Ahmet Küçükoğlu

Posted on • Originally published at ahmetkucukoglu.com

1

ASP.NET Core Feature Management

This article was originally published at: https://www.ahmetkucukoglu.com/en/asp-net-core-feature-management-en/

Feature Management provides feature management in .NET Core applications. It allows the management and querying of the active / passive status of the features of the application. For example, you can ensure that a feature you have just developed is active in a certain date range. To give another example, you can ensure that a feature you have developed is active with a certain percentage. Like A/B testing.

Let's test it by practicing. First, we need to install the package below.

dotnet add package Microsoft.FeatureManagement.AspNetCore -version 2.0.0
Enter fullscreen mode Exit fullscreen mode

Let's define our features in enum.

namespace FeatureManagementSample
{
public enum Features
{
FeatureA,
FeatureB,
FeatureC,
FeatureD
}
}
view raw Features.cs hosted with ❤ by GitHub

Let's activate the Feature Management in the Startup.

namespace FeatureManagementSample
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddFeatureManagement();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllers();
});
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

Because Feature Management reads features from IConfiguration, we can define features in environment variable, commandline argument or appsettings. Or, if you have a custom provider, you can define your features in the related source. We will use appsettings in our example. Let's define the feature named FeatureA.

{
"FeatureManagement": {
"FeatureA": true
}
}

We need to inject the "IFeatureManager" interface to query if the features are activated. By using the "IsEnabledAsync" method of this interface, we can query as follows if the "FeatureA" feature is activated.

namespace FeatureManagementSample.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;
[Route("api/[controller]")]
[ApiController]
public class FeatureController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public FeatureController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var featureA = await _featureManager.IsEnabledAsync(nameof(Features.FeatureA));
return Ok(featureA);
}
}
}
TimeWindowFilter

If we want the feature to be active in a certain date range, we can use the internal "TimeWindowFilter". For this, we need to activate this filter in the Startup.

namespace FeatureManagementSample
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddFeatureManagement()
.AddFeatureFilter<TimeWindowFilter>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllers();
});
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

Let's define the feature named FeatureB in appsettings.

{
"FeatureManagement": {
"FeatureB": {
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "Wed, 20 May 2020 14:53:00 GMT",
"End": "Wed, 20 May 2020 15:53:00 GMT"
}
}
]
}
}
}

UTC Date is used as the date format. If you only give the Start parameter, the feature will be active from this date. If you only give the End parameter, the feature will be active until this date.

Again, using the "IsEnabledAsync" method of the "IFeatureManager" interface, we can query if the "FeatureB" feature gets activated as follows.

namespace FeatureManagementSample.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;
[Route("api/[controller]")]
[ApiController]
public class FeatureController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public FeatureController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var featureB = await _featureManager.IsEnabledAsync(nameof(Features.FeatureB));
return Ok(featureB);
}
}
}
PercentageFilter

If we want the feature to be active with a certain percentage, we can use the internal "PercentageFilter". For this, we need to activate this filter in the Startup.

namespace FeatureManagementSample
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddFeatureManagement()
.AddFeatureFilter<TimeWindowFilter>()
.AddFeatureFilter<PercentageFilter>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllers();
});
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

Let's define the feature named FeatureC in appsettings.

{
"FeatureManagement": {
"FeatureC": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": {
"Value": 80
}
}
]
}
}

This feature will be active in 80% of the requests.

Again, using the "IsEnabledAsync" method of the "IFeatureManager" interface, we can query if the "FeatureC" feature gets activated as follows.

namespace FeatureManagementSample.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;
[Route("api/[controller]")]
[ApiController]
public class FeatureController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public FeatureController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var featureC = await _featureManager.IsEnabledAsync(nameof(Features.FeatureC));
return Ok(featureC);
}
}
}
Custom Filter

Apart from these filters, you can also write your own filters. For example, a filter can be written as below for the feature to be active on a mobile device.

Let's define the feature named FeatureD in appsettings.

{
"FeatureManagement": {
"FeatureD": {
"EnabledFor": [
{
"Name": "Mobile",
"Parameters": {
"Allowed": [ "Android" ]
}
}
]
}
}
}

Let's create "MobileFilterSettings" class to access filter parameters.

namespace FeatureManagementSample
{
using System.Collections.Generic;
public class MobileFilterSettings
{
public IList<string> Allowed { get; set; } = new List<string>();
}
}

Let's write our special filter named "MobileFilter" as follows.

namespace FeatureManagementSample
{
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.FeatureManagement;
using System;
using System.Threading.Tasks;
public class MobileFilter : IFeatureFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MobileFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
var isEnabled = false;
var settings = context.Parameters.Get<MobileFilterSettings>();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
foreach (var item in settings.Allowed)
{
if (userAgent.Contains(item, StringComparison.OrdinalIgnoreCase))
{
isEnabled = true;
break;
}
}
return Task.FromResult(isEnabled);
}
}
}
view raw MobileFilter.cs hosted with ❤ by GitHub

We need to activate this filter in the Startup.

namespace FeatureManagementSample
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddFeatureManagement()
.AddFeatureFilter<TimeWindowFilter>()
.AddFeatureFilter<PercentageFilter>()
.AddFeatureFilter<MobileFilter>();
services.AddHttpContextAccessor();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllers();
});
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

Again, using the "IsEnabledAsync" method of the "IFeatureManager" interface, we can query if "FeatureD" feature gets activated as follows.

namespace FeatureManagementSample.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;
[Route("api/[controller]")]
[ApiController]
public class FeatureController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public FeatureController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var featureD = await _featureManager.IsEnabledAsync(nameof(Features.FeatureD));
return Ok(featureD);
}
}
}
Mvc Action Filter Attribute

Instead of using the IFeatureManager interface, you can set the activation of the attribute based Controller or Action, thanks to ActionFilter.

namespace FeatureManagementSample.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
[Route("api/[controller]")]
[ApiController]
public class FeatureController : ControllerBase
{
[HttpGet]
[FeatureGate(nameof(Features.FeatureA))]
public IActionResult Get()
{
return Ok();
}
}
}
Razor Tag Helper

You can use the feature razor tag if you want to display the content in the View according to the activation status of the feature.

<feature name="@nameof(Features.FeatureA)">
<p>FeatureA özelliği aktif ise burası görüntülenir.</p>
</feature>
view raw Feature.cshtml hosted with ❤ by GitHub
Middleware

If you want to add middleware according to the activation status of the feature, you can use the UseMiddlewareForFeature method.

app.UseMiddlewareForFeature<FeatureAMiddleware>(nameof(Features.FeatureA));
view raw Startup.cs hosted with ❤ by GitHub

You can access the sample project from Github.

Good luck.

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay