Hasim.Injectify 0.1.7

dotnet add package Hasim.Injectify --version 0.1.7
                    
NuGet\Install-Package Hasim.Injectify -Version 0.1.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hasim.Injectify" Version="0.1.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hasim.Injectify" Version="0.1.7" />
                    
Directory.Packages.props
<PackageReference Include="Hasim.Injectify" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hasim.Injectify --version 0.1.7
                    
#r "nuget: Hasim.Injectify, 0.1.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Hasim.Injectify@0.1.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Hasim.Injectify&version=0.1.7
                    
Install as a Cake Addin
#tool nuget:?package=Hasim.Injectify&version=0.1.7
                    
Install as a Cake Tool

Injectify

Build NuGet NuGet Downloads .NET License

A modular dependency injection system for ASP.NET Core applications that eliminates manual service registration entirely.

Why Injectify?

The Problem

In traditional ASP.NET Core, each new service requires manual registration:

// Every single service needs manual registration 😫
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IOrderService, OrderService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IEmailService, EmailService>();
// ... 50+ more services ...

The Injectify Solution

Implement a marker interface β†’ automatically registered ✨

// Just inherit the marker interface β€” that's it!
public interface IUserService : IAddScoped { }
public class UserService : IUserService { }

public interface ICacheService : IAddSingleton { }
public class CacheService : ICacheService { }

// Everything is auto-discovered and registered with just:
builder.InjectifyApplication();

Concept

Each project in your solution (Application, Core, EF Core, UI, etc.) defines its own module that registers its own dependencies. The host project (UI) discovers and initializes all modules automatically with just two calls:

// Program.cs β€” that's literally all you need
var builder = WebApplication.CreateBuilder(args);
builder.InjectifyApplication();  // Auto-discovers modules + auto-registers all services
var app = builder.Build();
app.InjectifyInitializer();       // Configures app pipeline
app.Run();

No manual service registration. No configuration files. Just implement marker interfaces.

Core Features

Feature Description Impact
πŸš€ Zero Manual Registration Implement marker interfaces β€” services auto-register. No services.Add*() calls needed. Eliminates ~70% of boilerplate
πŸ“¦ Auto-Discovery Recursively scans all assemblies, discovers all IModule implementations automatically One call: builder.InjectifyApplication()
🎯 Marker-based Auto DI IAddSingleton, IAddScoped, IAddTransient on your service β†’ auto-registered by lifetime No more manual .AddScoped<I, T>()
πŸ”„ Module Lifecycle OnInit β†’ ConfigureService β†’ ConfigureApplication β†’ OnStart β†’ OnStop Full control over initialization order
πŸ“Š Dependency Graph Topological sort, cycle detection, dependency validation Fail-fast on misconfiguration
🏷️ Module Metadata [ModuleInfo], [ModuleOrder], [DependsOn] for organization Self-documenting modules
⚑ Conditional Loading Load modules based on environment, config, or custom predicates Feature flags, environment-specific modules

Installation

dotnet add package Injectify

Quick Start

1. Create a module in each project (optional for DI-only services)

using Injectify.Modules;

[ModuleInfo("My Module", "1.0.0", "Description of the module")]
[ModuleOrder(10)]
public class MyModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<IMyService, MyService>();
    }

    public override void ConfigureApplication(WebApplication app)
    {
        app.Logger.LogInformation("MyModule initialized");
        app.UseMiddleware<MyMiddleware>();
    }
}

2. In the UI project β€” 2 lines (discovers modules + auto-registers services)

var builder = WebApplication.CreateBuilder(args);
builder.InjectifyApplication();    // Scans all assemblies, discovers all IModule + auto-registers IAddSingleton/IAddScoped/IAddTransient

var app = builder.Build();
app.InjectifyInitializer();            // Calls ConfigureApplication on each module
app.Run();

3. Auto-Register Services with Marker Interfaces (Zero Boilerplate!)

This is the killer feature. Instead of manual registration, just inherit a marker interface:

Traditional approach (verbose):
// Your Program.cs requires THIS for every single service
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IOrderService, OrderService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddSingleton<IConfigService, ConfigService>();
services.AddTransient<IEmailService, EmailService>();
// ... repeat 50+ times for a real app 😩
Injectify approach (automatic):
using Injectify.Abstractions;

// **That's it.** Just add the marker interface.

// Automatically registered as Scoped
public interface IUserService : IAddScoped { }
public class UserService : IUserService { }

// Automatically registered as Singleton  
public interface ICacheService : IAddSingleton { }
public class CacheService : ICacheService { }

// Automatically registered as Transient
public interface IEmailService : IAddTransient { }
public class EmailService : IEmailService { }

One line in Program.cs does it all:

builder.InjectifyApplication();  // Discovers + auto-registers every IAddSingleton/Scoped/Transient

Benefits:

  • βœ… No boilerplate registration code
  • βœ… Services are self-documenting (lifetime is in the interface name)
  • βœ… New developers can't forget to register a service
  • βœ… Scales from 10 to 1000+ services without friction

4. Module lifecycle

public class MyModule : BaseInjectifyModule
{
    public override void OnInit(IServiceCollection services) { /* before registration */ }
    public override void OnStart(WebApplication app)        { /* after build, before run */ }
    public override void OnStop()                           { /* on shutdown */ }
}

5. Module dependencies

[DependsOn(typeof(CoreModule))]
[DependsOn(typeof(EFCoreModule))]
public class UIModule : BaseInjectifyModule { }

6. Conditional Module Loading

Load modules conditionally based on environment, configuration, or custom logic.

A) Environment-based loading

Load a module only in specific environments (Production, Development, Staging, etc.):

using Injectify.Attributes;
using Injectify.Modules;

// Load only in Production
[ConditionalModule(Environments = "Production")]
public class ProductionAnalyticsModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IAnalyticsService, ProductionAnalyticsService>();
    }
}

// Load in Production OR Staging (semicolon-separated)
[ConditionalModule(Environments = "Production;Staging")]
public class MonitoringModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IMonitoringService, MonitoringService>();
    }
}

How it works:

  • Environments = "Production" β†’ loads only when IHostEnvironment.EnvironmentName == "Production"
  • Environments = "Production;Staging" β†’ loads if environment is Production OR Staging
  • Multiple environments separated by ; or ,
B) Configuration-based loading

Load a module if a configuration setting is enabled:

// appsettings.json or appsettings.Development.json
{
  "Features": {
    "Analytics": {
      "Enabled": true
    },
    "Authentication": {
      "OAuth": {
        "Enabled": false
      }
    }
  }
}

// Load only if Features:Analytics:Enabled is true
[ConditionalModule(ConfigurationPath = "Features:Analytics:Enabled")]
public class AnalyticsModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IAnalyticsEngine, AnalyticsEngine>();
    }
}

// OAuth module loads only if the config key is true
[ConditionalModule(ConfigurationPath = "Features:Authentication:OAuth:Enabled")]
public class OAuthModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<IOAuthService, OAuthService>();
    }
}

How it works:

  • ConfigurationPath = "Features:Analytics:Enabled" β†’ loads if config["Features:Analytics:Enabled"] == "true"
  • Configuration value must be a boolean-parseable string ("true" or "false")
  • If the key is missing, the module is skipped
C) Custom predicate loading

Load a module based on custom logic:

using Injectify.Abstractions;
using Injectify.Attributes;
using Injectify.Modules;

// Define a custom condition
public class IsLicensedCondition : IModuleCondition
{
    private readonly ILicenseService _licenseService;
    
    public IsLicensedCondition(ILicenseService licenseService)
    {
        _licenseService = licenseService;
    }

    public async Task<bool> IsSatisfiedAsync()
    {
        // Custom logic: check if premium license is active
        return await _licenseService.IsActivePremiumLicenseAsync();
    }
}

// Load only if the condition is satisfied
[ConditionalModule(ConditionType = typeof(IsLicensedCondition))]
public class PremiumFeaturesModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<IAdvancedReportingService, AdvancedReportingService>();
        services.AddScoped<ICustomBrandingService, CustomBrandingService>();
    }
}

How it works:

  • Create a class implementing IModuleCondition
  • Implement IsSatisfiedAsync() β†’ return true to load the module, false to skip it
  • Can be async (e.g., call a database or API to decide)
  • Dependencies are resolved via DI container
D) Hybrid conditions (combine multiple types)

Combine environment + configuration + custom conditions β€” module loads if ANY condition is satisfied (OR logic):

// Load in Production, OR if config enables debug mode, OR if custom condition is satisfied
[ConditionalModule(
    Environments = "Production",
    ConfigurationPath = "Features:DebugMode:Enabled",
    ConditionType = typeof(IsAdminUserCondition)
)]
public class DiagnosticsModule : BaseInjectifyModule
{
    public override void ConfigureService(IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IDiagnosticsService, DiagnosticsService>();
    }
}

This module loads if:

  • Environment is Production, OR
  • Config key "Features:DebugMode:Enabled" is true, OR
  • Custom condition IsAdminUserCondition is satisfied
Complete example: Environment + Feature flags
// appsettings.Development.json
{
  "Features": {
    "Development": {
      "HotReload": true
    }
  }
}

// In your module
[ConditionalModule(
    Environments = "Development",
    ConfigurationPath = "Features:Development:HotReload"
)]
public class HotReloadModule : BaseInjectifyModule
{
    public override void ConfigureApplication(WebApplication app)
    {
        app.Logger.LogInformation("Hot reload enabled in Development");
        // Configure hot reload middleware
    }
}

Performance Benchmarks

Baseline performance metrics measured with BenchmarkDotNet on .NET 8.0 (Intel Core i5-6300U).

Key Metrics

Scenario Time Memory
Discovery + Registration (current assembly) 5.67 ΞΌs 4.28 KB
Discovery + Registration (Injectify assembly) 9.52 ΞΌs 4.83 KB
Singleton Registration 3.83 ΞΌs 3.78 KB
Scoped Registration 4.96 ΞΌs 3.78 KB
Transient Registration 4.38 ΞΌs 3.78 KB
Single Assembly Startup 5.34 ΞΌs 4.28 KB
Multiple Assemblies Startup 13.13 ΞΌs 5.60 KB

Injectify vs Manual Registration

Method Time Memory Overhead
Injectify Auto-Registration 5.11 ΞΌs 4.28 KB +28% time
Manual Registration 3.98 ΞΌs 3.95 KB baseline

Analysis: Injectify's auto-discovery and registration adds ~1.1 microseconds of overhead per operation, with slightly higher memory footprint (330 bytes). For typical applications, this represents negligible performance impact compared to the boilerplate elimination benefit.

Dynamic Feature Management

Enable or disable business features at runtime, without restarting the application. Changes are persisted in the database and are visible on the next check.

Three packages work together:

Package Purpose
Hasim.Injectify.Features Feature declaration, IInjectifyFeatureManager, startup synchronization
Hasim.Injectify.Features.EntityFrameworkCore Persistence with EF Core (SQLite, SQL Server, PostgreSQL, ...)
Hasim.Injectify.Features.AspNetCore MVC, Razor Pages, Blazor and minimal API integration

1. Declare features and choose the database

using Injectify.Features.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddInjectifyFeatures(options =>
{
    options.AddFeature("Reports.Export", "Export des rapports", enabledByDefault: false);
    options.AddFeature("Notifications.Email", "Notifications par e-mail", enabledByDefault: true);
});

// Dedicated feature database. Use any EF Core provider.
builder.Services.AddInjectifyFeaturesEntityFrameworkCore(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("Injectify")));

To store features in your own DbContext, call ApplyInjectifyFeatures() in its OnModelCreating and register the context:

using Injectify.Features.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyInjectifyFeatures();
    }
}

builder.Services.AddDbContextFactory<AppDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddInjectifyFeaturesEntityFrameworkCore<AppDbContext>();

Migrations are not shipped with the package. Generate them in your application, one set per database engine:

dotnet ef migrations add InitialFeatures --project MyApp.Migrations.Sqlite

2. Check a feature in business code

public class ReportService
{
    private readonly IInjectifyFeatureManager _features;

    public ReportService(IInjectifyFeatureManager features) => _features = features;

    public async Task ExportAsync(CancellationToken cancellationToken)
    {
        if (!await _features.IsEnabledAsync("Reports.Export", cancellationToken))
            throw new FeatureDisabledException("Reports.Export");

        // Run the business operation.
    }
}

An unknown feature is reported as disabled. Use TryGetStateAsync to tell an unknown feature from a disabled one.

3. Change a feature at runtime

// Persisted before the call returns. Takes effect on the next check.
await manager.SetEnabledAsync("Reports.Export", true, modifiedBy: "admin@example.com", cancellationToken);

The package does not expose an admin API by itself. Protect the endpoint that calls this method with an authorization policy, and return 404 for an unknown feature:

using System.Security.Claims;

builder.Services.AddAuthorization(options =>
    options.AddPolicy("FeatureAdmin", policy => policy.RequireRole("Administrator")));

app.MapPost("/admin/features/{name}", async (
    string name,
    bool enabled,
    IInjectifyFeatureManager manager,
    ClaimsPrincipal user,
    CancellationToken cancellationToken) =>
{
    try
    {
        await manager.SetEnabledAsync(name, enabled, user.Identity?.Name, cancellationToken);
        return Results.NoContent();
    }
    catch (FeatureNotFoundException)
    {
        return Results.NotFound();
    }
}).RequireAuthorization("FeatureAdmin");

Configure your authentication scheme as well: the policy above only states the rule.

4. Hide UI and protect pages

MVC and Razor Pages: add the tag helper to _ViewImports.cshtml:

@addTagHelper *, Injectify.Features.AspNetCore
<injectify-feature name="Reports.Export">
    <a href="/reports/export">Export</a>
</injectify-feature>

Protect a controller action or a Razor Page with the [RequireFeature] attribute. A disabled or unknown feature returns 404:

public class ReportsController : Controller
{
    [RequireFeature("Reports.Export")]
    public IActionResult Export() => View();
}

Razor Pages: apply the same attribute to the page model. A disabled or unknown feature returns 404:

[RequireFeature("Reports.Export")]
public class ExportModel : PageModel
{
    public void OnGet() { }
}

With Pages/Export.cshtml containing @page "/reports/export-page" and @model ExportModel.

Blazor: use the FeatureGate component. Add the namespace to _Imports.razor:

@using Injectify.Features.AspNetCore.Components
<FeatureGate Name="Reports.Export">
    <button @onclick="Export">Export</button>
</FeatureGate>

Minimal APIs: add RequireFeature to an endpoint:

app.MapGet("/reports/export", () => Results.Ok("exported"))
   .RequireFeature("Reports.Export");

Hiding a button does not protect the operation. Keep the check in the business code or on the endpoint.

5. Run the tests

Each package has its own test project. Run them individually:

dotnet test Injectify.Tests/Injectify.Tests.csproj                                                     # core modules
dotnet test Injectify.Features.Tests/Injectify.Features.Tests.csproj                                   # feature manager
dotnet test Injectify.Features.EntityFrameworkCore.Tests/Injectify.Features.EntityFrameworkCore.Tests.csproj  # EF Core store (SQLite)
dotnet test Injectify.Features.AspNetCore.Tests/Injectify.Features.AspNetCore.Tests.csproj             # MVC, minimal API, TagHelper

The EF Core tests run on temporary SQLite files by default. To also run them against a local SQL Server (no Docker), set the INJECTIFY_TEST_SQLSERVER environment variable to a server connection string; each test then creates and drops its own database. Without the variable, the SQL Server tests are reported as skipped. The solution file Injectify.slnx requires the .NET 9.0.200+ SDK; with the .NET 8 SDK, build the projects individually.

Packages

Package Description
Injectify Module system with DI registration, pipeline config, and module lifecycle
Hasim.Injectify.Features Dynamic feature management: declaration, IInjectifyFeatureManager, startup synchronization
Hasim.Injectify.Features.EntityFrameworkCore EF Core persistence for feature states (any EF Core provider)
Hasim.Injectify.Features.AspNetCore MVC, Razor Pages, Blazor and minimal API integration for features

Project structure

Injectify/
β”œβ”€β”€ Abstractions/          # Public contracts: IModule, IModuleLifecycle, IModuleLoader, DI markers
β”œβ”€β”€ Attributes/            # [DependsOn], [ModuleInfo], [ModuleOrder]
β”œβ”€β”€ Exceptions/            # InjectifyException, DependencyCycleException
β”œβ”€β”€ Extensions/            # Fluent DI extensions (InjectifyApplication, InjectifyInitializer, DependencyInjectionExtensions)
β”œβ”€β”€ Modules/               # BaseInjectifyModule
β”œβ”€β”€ Setup/                 # ModuleLoader, ModuleDependencySorter (assembly scanning, topological sort)
└── Injectify.Tests/       # Unit tests (xUnit)

Documentation

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Hasim.Injectify:

Package Downloads
Hasim.Auditify

A lightweight, pluggable audit-trail library for EF Core 8+. Automatically captures entity changes (Added/Modified/Deleted), supports sensitive data masking, soft-delete patterns, and timestamp tracking.

Hasim.EntityFrameworkCore

A customizable ASP.NET Core Identity entities library providing reusable base identity models and common abstractions for modern .NET applications.

Hasim.Infrastructure

Todo Description.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.7 43 10/26/2026
0.1.6 141 9/21/2026
0.1.5 103 9/21/2026
0.1.4 171 8/28/2026
0.1.3 116 8/27/2026
0.1.2 335 7/30/2026
0.1.1 243 7/30/2026
0.1.0 178 7/30/2026