DEV Community

Cover image for I Built a Feature Module Engine in .NET — Here's the Architecture That Made It Click
Mario Alberto Arce
Mario Alberto Arce

Posted on

I Built a Feature Module Engine in .NET — Here's the Architecture That Made It Click

After 20+ years building enterprise C# systems, one pattern kept showing up as a pain point: you ship a package, and every consumer of that package inherits every behavior — enabled or not, needed or not, tested or not.

I wanted to flip that. So I built the PowerCSharp Features engine.

The Problem With Traditional Extension Methods

// Everyone gets CORS configured. No opt-out. No flag. No context.
services.AddCorsPowerCSharp();
Enter fullscreen mode Exit fullscreen mode

The library decides.
The consumer accepts.

A Better Model: Module + Flag + Context

// The engine discovers this automatically from opted-in assemblies.
public class CorsFeatureModule : IFeatureModule
{
    public string FeatureKey => "Cors";
    public int Order => 10;

    public void ConfigureServices(IFeatureRegistrationContext context)
    {
        if (!context.Flags.IsEnabled(FeatureKey))
        {
            return; // safe-off: do nothing, or register a NoOp
        }

        context.Services.Configure<CorsFeatureOptions>(
            context.Configuration.GetSection("PowerFeatures:Cors"));
        // register active CORS policy
    }

    public void ConfigurePipeline(IFeaturePipelineContext context)
    {
        context.App.UseCors("PowerCSharpCors");
    }
}
Enter fullscreen mode Exit fullscreen mode

The module is self-contained.
The engine calls it.
The consumer configures a flag.
The behavior follows the flag.

How Flag Resolution Works

The engine composes a composite resolver from multiple providers:

Code override → Custom IFeatureFlagProvider → Environment variables → appsettings → Feature default
Enter fullscreen mode Exit fullscreen mode

This means:

  • appsettings.json → controlled by ops
  • Environment variables → controlled by infrastructure/K8s
  • Code override → controlled by engineering for testing
  • Custom provider → controlled by your Azure App Config / AWS SSM / database

The same binary ships to all environments. Only flags differ.

What the Engine Does for You

  1. Discovery — scans opted-in assemblies for IFeatureModule implementations (reflection, opt-in only — no surprise scanning)
  2. Flag resolution — composites all providers in precedence order
  3. DI orchestration — calls ConfigureServices on every module; modules self-gate
  4. Registry — records the resolved state of every feature (key, tier, enabled, source, version)
  5. Pipeline application — calls ConfigurePipeline in Order for enabled, middleware-bearing modules
  6. Diagnostics — structured startup log + opt-in HTTP endpoint

Host Integration (The Full Picture)

// Program.cs
builder.Services.AddPowerFeatures(builder.Configuration, options =>
{
    options.AddBuiltInFeatures();                             // Group 1: bundled capabilities
    options.ScanAssemblies(typeof(CacheFeatureModule).Assembly); // Group 2: pluggable features
    options.Override("Cache", true);                          // engineering override
    options.EnableDiagnosticsEndpoint();                      // GET /power-features
});

var app = builder.Build();
app.UsePowerFeatures(); // applies enabled middleware; logs resolved matrix
Enter fullscreen mode Exit fullscreen mode

Why I Chose This Design

  • No reflection surprises. Nothing is scanned unless you explicitly opt-in an assembly.
  • ASP.NET Core conventions. AddXxx / UseXxx — developers already know this pattern.
  • Safe-off by default. Every module registers a NoOp when disabled. Dependents always resolve.
  • Zero third-party abstractions. PowerCSharp.Features.Abstractions has no NuGet dependencies.

The Contracts Are Open

// Implement this to build any feature
public interface IFeatureModule
{
    string FeatureKey { get; }
    int Order { get; }
    void ConfigureServices(IFeatureRegistrationContext context);
    void ConfigurePipeline(IFeaturePipelineContext context);
}

// Implement this to build any flag source
public interface IFeatureFlagProvider
{
    bool IsEnabled(string featureKey);
    FeatureFlagValue GetValue(string featureKey);
}
Enter fullscreen mode Exit fullscreen mode

Fork the repo, implement the interface, ship your own feature package. The engine handles the rest.

GitHub: https://github.com/marioarce/PowerCSharp

NuGet: PowerCSharp.Features / PowerCSharp.Features.Abstractions

Top comments (0)