🎯 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.