Feature Flags with Minimal Endpoints

Problem to solve : Enable/Disable endpoints based on feature flags

We don't want to have the implementation of our endpoints on Program.cs, let's clean it up to be like this

var builder = WebApplication.CreateBuilder(args);
 
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
 
var app = builder.Build();
 
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}
 
app.UseHttpsRedirection();
 
app.Run();



For a more elegant way to register we create an interface called IMinimalEndpoint and add the following code

public interface IMinimalEndpoint
{
    void MapEndpoint(IEndpointRouteBuilder routeBuilder);
}



Add a static class with the name MinimalEndpointExtensions that will discover all the implementations of IMinimalEndpoint and register on our container, and then call the MapEndpoint of each one to create the routes.

public static class MinimalEndpointExtensions
{
    public static IServiceCollection AddMinimalEndpoints(this IServiceCollection services)
    {
        var assembly = typeof(Program).Assembly;
        var serviceDescriptors = assembly
            .DefinedTypes
            .Where(type => type is { IsAbstract: false, IsInterface: false } &&
                           type.IsAssignableTo(typeof(IMinimalEndpoint)))
            .Select(type => ServiceDescriptor.Transient(typeof(IMinimalEndpoint), type));
        services.TryAddEnumerable(serviceDescriptors);
        return services;
    }
 
 
    public static IApplicationBuilder MapMinimalEndpoints(this WebApplication app)
    {
        var endpoints = app.Services
            .GetRequiredService<IEnumerable<IMinimalEndpoint>>();
        foreach (var endpoint in endpoints)
        {
            endpoint.MapEndpoint(app);
        }
        return app;
    }
}



Now that we already have the process to find and register the endpoints, let's create our endpoints.

  • FeatureAttributeEndpoint : Endpoint that will be enabled or disabled based on an attribute.
  • IfConditionEndpoint : Endpoint that will be enabled or disabled based on a condition.
  • OpenApiEndpoint : Open Endpoint that will always be enabled.
public class OpenEndpoint : IMinimalEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder routeBuilder)
    {
        routeBuilder.MapGet("/openendpoint", () => WeatherForecast.GetWeatherForecasts(1));
    }
}
 
public class IfConditionEndpoint : IMinimalEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder routeBuilder)
    {
        routeBuilder.MapGet("/ifconditionendpoint", () => WeatherForecast.GetWeatherForecasts(2));
    }
}
 
public class FeatureAttributeEndpoint : IMinimalEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder routeBuilder)
    {
        routeBuilder.MapGet("/featureattributeendpoint", () => WeatherForecast.GetWeatherForecasts(3));
    }
}

And just add a class to generate our dummy data

public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
 
    public static IEnumerable<WeatherForecast> GetWeatherForecasts(int count )
    {
        var summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
 
        return Enumerable.Range(1, count).Select(index =>
            new WeatherForecast
            (
                DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                Random.Shared.Next(-20, 55),
                summaries[Random.Shared.Next(summaries.Length)]
            ));
    }
}



And on the Program.cs we just need to add the following lines to register the endpoints

...
builder.Services.AddMinimalEndpoints();
 
var app = builder.Build();
 
...
 
app.MapMinimalEndpoints();
 
app.Run();

Now if you run and go the /openendpoint , /ifconditionendpoint and /featureattributeendpoint you will see the data.

Let's add the feature management to enable or disable the endpoints based on feature flags.

Install the Microsoft.FeatureManagement.AspNetCore package and then add the configuration for the feature Management in the appsettings.json file. This line will be used to enable or disable the feature flag that we called of AllowApi.

"FeatureManagement" : {
    "AllowApi" : false
}



On Programs.cs add the following line to register the feature management

...
builder.Services.AddMinimalEndpoints();
 
builder.Services.AddFeatureManagement();
var app = builder.Build();
...



On the IfConditionEndpoint class we will change to use the feature flag to enable or disable the endpoint

public class IfConditionEndpoint(IFeatureManager featureManager) : IMinimalEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder routeBuilder)
    {
        if (!featureManager.IsEnabledAsync("AllowApi").Result)
        {
            return;
        }
 
        routeBuilder.MapGet("/ifconditionendpoint", () => WeatherForecast.GetWeatherForecasts(2));
    }
}

If you run and go the /openendpoint you will see the data, but if you go to /ifconditionendpoint you will get a 404 error.

To enable/disable the FeatureAttributeEndpoint based on the feature flag we will first create an attribute called FeatureEnableAttribute

public class FeatureEnableAttribute(string featureName) : Attribute
{
    public string FeatureName { get; } = featureName;
}

Then we need to change the MinimalEndpointExtensions to check if the endpoint has the FeatureEnableAttribute and if it has, check if the feature flag is enabled.

public static IApplicationBuilder MapMinimalEndpoints(this WebApplication app)
{
    var featureManager = app.Services.GetRequiredService<IFeatureManager>();
    var endpoints = app.Services
        .GetRequiredService<IEnumerable<IMinimalEndpoint>>();
    foreach (var endpoint in endpoints)
    {
        var attribute = endpoint.GetType().GetCustomAttribute<FeatureEnableAttribute>();
        if (attribute != null && !featureManager.IsEnabledAsync(attribute.FeatureName).Result)
        {
            continue;
        }
        endpoint.MapEndpoint(app);
    }
    return app;
}

On the FeatureAttributeEndpoint class we will add the attribute to enable or disable the endpoint

[FeatureEnable("AllowApi")]
public class FeatureAttributeEndpoint : IMinimalEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder routeBuilder)
    {
        routeBuilder.MapGet("/featureattributeendpoint", () => WeatherForecast.GetWeatherForecasts(3));
    }
}

Now if you run and go the /openendpoint you will see the data, but if you go to /ifconditionendpoint or /featureattributeendpoint you will get a 404 error.

To enable the /ifconditionendpoint and /featureattributeendpoint you need to change the AllowApi to true in the appsettings.json file.

"FeatureManagement" : {
    "AllowApi" : true
}

And now if you go to /ifconditionendpoint or /featureattributeendpoint you will see the data.


Hope this helps on your journey. Happy coding! 🚀