Hasim.Injectify
0.1.7
dotnet add package Hasim.Injectify --version 0.1.7
NuGet\Install-Package Hasim.Injectify -Version 0.1.7
<PackageReference Include="Hasim.Injectify" Version="0.1.7" />
<PackageVersion Include="Hasim.Injectify" Version="0.1.7" />
<PackageReference Include="Hasim.Injectify" />
paket add Hasim.Injectify --version 0.1.7
#r "nuget: Hasim.Injectify, 0.1.7"
#:package Hasim.Injectify@0.1.7
#addin nuget:?package=Hasim.Injectify&version=0.1.7
#tool nuget:?package=Hasim.Injectify&version=0.1.7
Injectify
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 whenIHostEnvironment.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
- README.fr.md β Documentation en franΓ§ais
- AUDIT.md β Audit findings & technical debt
- docs/architecture.md β Architecture and dependency flow
License
MIT
| Product | Versions 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. |
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
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.