.NET Core  

Implementing a Dynamic Feature Gate System in .NET 10 with PostgreSQL and Clean Architecture

🎯 Introduction

In modern software development, the ability to enable or disable features at runtime without redeploying the application is a game-changer. This technique, known as Feature Gating or Feature Toggling, allows teams to perform A/B testing, manage gradual rollouts, and kill buggy features instantly.

In this article, we will build a dynamic Feature Gate system using .NET 10, PostgreSQL, and Clean Architecture. We’ll implement a custom Action Filter that checks the database before allowing access to specific API endpoints.

1. Project Architecture

To keep the project maintainable and scalable, we follow Clean Architecture principles within a single project structure:

  • Domain: Contains our core entities (e.g.,

    Feature).

  • Application: Contains interfaces and services (e.g.,

    IFeatureService).

  • Infrastructure: Handles data persistence with EF Core and PostgreSQL.

  • Web/API: Contains controllers, middleware, and our custom FeatureGateAttribute.

2. Defining the Domain

Our system revolves around a simple

Featureentity that tracks whether a feature is active.

namespace FeatureGate.Api.Domain.Entities;

public class Feature
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public bool IsEnabled { get; set; }
    public string Description { get; set; } = string.Empty;
}

3. The Infrastructure Layer

We use Entity Framework Core with Npgsql to connect to PostgreSQL.

ApplicationDbContext

We seed the database with initial feature flags during the migration process.

modelBuilder.Entity<Feature>().HasData(
    new Feature { Id = 1, Name = "UserView", IsEnabled = true, Description = "Enable viewing users" },
    new Feature { Id = 2, Name = "UserCreate", IsEnabled = false, Description = "Enable creating users" },
    new Feature { Id = 3, Name = "UserUpdate", IsEnabled = true, Description = "Enable updating users" },
    new Feature { Id = 4, Name = "UserDelete", IsEnabled = false, Description = "Enable deleting users" }
);

4. The Magic: Custom FeatureGate Attribute

The core of this system is the FeatureGateAttribute. This attribute implements IAsyncActionFilter, allowing it to intercept requests before they reach the controller action.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureGateAttribute : Attribute, IAsyncActionFilter
{
    private readonly string _featureName;
    public FeatureGateAttribute(string featureName)
    {
        _featureName = featureName;
    }
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var featureService = context.HttpContext.RequestServices.GetRequiredService<IFeatureService>();
        
        if (!await featureService.IsFeatureEnabledAsync(_featureName))
        {
            // Return 403 Forbidden with a friendly message
            context.Result = new ObjectResult(new { message = $"Feature '{_featureName}' is currently disabled." }) 
            { 
                StatusCode = StatusCodes.Status403Forbidden 
            };
            return;
        }
        await next();
    }
}

5. Protecting Endpoints

Now, we can simply decorate our controller actions with the [FeatureGate] attribute.

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    [FeatureGate("UserView")] // Gated!
    public IActionResult Get() => Ok(_userService.GetAll());
    [HttpPost]
    [FeatureGate("UserCreate")] // Gated!
    public IActionResult Post(User user)
    {
        _userService.Create(user);
        return Ok(user);
    }
}

6. Real-Time Updates

To manage these features, we provide a

FeaturesController. By hitting aPUTendpoint, an administrator can toggle a feature in the PostgreSQL database. The next time a user hits a gated endpoint, theFeatureGateAttributewill fetch the updated status, providing instant control without any downtime.

Conclusion

By combining the power of .NET 10 filters with a persistent store like PostgreSQL, you can create a robust and flexible Feature Gate system. This approach follows Clean Architecture, making it easy to swap PostgreSQL for Redis or any other store in the future.

Key Benefits:

  • Operational Control: Disable features instantly if they cause issues.

  • Clean Code: Decouple gating logic from business logic using Attributes.

  • Scalability: Use caching (like Redis) to handle high-frequency checks.