Type discovery and convention-based service registration with automatic assembly scanning, interface mapping, and plugin architecture support for simplified dependency injection configuration.
$ dotnet add package Myth.DependencyInjection
A .NET library that simplifies dependency injection with automatic type discovery and convention-based service registration. Eliminate boilerplate code and enable plugin architectures with powerful assembly scanning and type resolution capabilities.
In enterprise .NET applications, manual service registration becomes a productivity killer. Imagine maintaining hundreds of repository, service, and handler registrations by hand—every new class requires three places to change (interface, implementation, registration). One forgotten registration causes runtime errors. Code reviews get cluttered with registration changes. Myth.DependencyInjection solves this once and for all.
Manual Registration Hell
// Startup.cs becomes a nightmare to maintain
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<IInvoiceRepository, InvoiceRepository>();
// ... 200 more lines ...
// New developer adds IPaymentRepository but forgets to register it → runtime crash
Problems with manual registration:
Convention-Based Auto-Registration
// One line registers ALL repositories following your naming convention
services.AddServiceFromType<IRepository>();
// One line registers ALL command handlers
services.AddServiceFromType<ICommandHandler>(ServiceLifetime.Transient);
Add a new repository? Just follow the naming convention (IUserRepository → UserRepository). It's automatically discovered and registered. No Startup.cs changes. No forgotten registrations. It just works.
| Aspect | Myth.DependencyInjection | Manual Registration | Scrutor/Other Tools |
|---|---|---|---|
| Lines of Code | 1 line per layer | 1 line per service | Multiple configuration calls |
| Maintenance | Add new service, done | Update Startup.cs every time | Configure scanning rules |
| Runtime Safety | Convention enforced | Easy to forget | Depends on configuration |
| Developer Experience | Intuitive naming convention | Tedious and error-prone | Learning curve for API |
| Type Discovery | Built-in TypeProvider | Manual reflection | Limited or missing |
| Plugin Support | Native type scanning | Complex custom code | Not primary focus |
| Performance | Optimized assembly scanning | N/A (manual) | Varies |
CQRS-Based Microservices Auto-register 100+ command/query handlers without touching Startup.cs. Convention ensures all handlers follow the same pattern.
Multi-Tenant SaaS Platforms Dynamically discover and register tenant-specific services. TypeProvider enables plugin-based multi-tenancy where each tenant can load custom implementations.
Domain-Driven Design (DDD) Applications Auto-register repositories by layer (IRepository, IDomainService, IApplicationService). Clean separation without registration clutter.
Modular Monolith Architecture
Each module registers its services with one call. Adding a new module is as simple as calling AddServiceFromType<IModuleService>().
Plugin-Based Architectures Build extensible systems where plugins drop into a folder and are automatically discovered, loaded, and registered. No hardcoded plugin lists.
🎯 Convention Over Configuration
Follow a simple naming convention (IUserRepository → UserRepository) and registration is automatic. No XML, no attributes, no manual wiring.
🔍 Powerful Type Discovery TypeProvider gives you assembly scanning, type filtering, and namespace detection out of the box. Build code generators, analyzers, and plugin systems easily.
⚡ Minimal Ceremony One line of code to register an entire layer. Reduces Startup.cs by 80-90% in large applications.
🏗️ Architecture Enforcement Naming conventions enforce consistency. Teams naturally follow patterns because that's how services get registered.
📦 Seamless Plugin Support
Scan assemblies at runtime, discover types implementing IPlugin, and dynamically load them. Perfect for extensible SaaS platforms.
🧪 Testability TypeProvider makes it trivial to write tests that verify all handlers are registered, or that your architecture rules are followed.
Convention over Configuration Inspired by Ruby on Rails and ASP.NET MVC, Myth.DependencyInjection reduces boilerplate by inferring registration from conventions rather than explicit configuration.
Reflection and Assembly Scanning Uses .NET's reflection capabilities to scan assemblies, discover types, and build the dependency graph automatically. Inspired by tools like Scrutor and StructureMap.
Marker Interface Pattern
Use marker interfaces (e.g., IRepository, IDomainService) to categorize services by architectural layer, then register entire layers with one call.
Plugin Architecture Enables Inversion of Control at the architectural level—your application doesn't know what plugins exist; they declare themselves by implementing interfaces.
Fail-Fast Principle
Throws InterfaceNotFoundException at startup (not runtime) if conventions aren't followed, ensuring problems are caught early.
For Developers
For Architects
For Product Teams
dotnet add package Myth.DependencyInjection
Register all implementations of an interface automatically based on naming conventions:
using Microsoft.Extensions.DependencyInjection;
using Myth.Extensions;
var services = new ServiceCollection();
// Automatically finds and registers all repository implementations
services.AddServiceFromType<IRepository>();
// Result: IPersonRepository -> PersonRepository (Scoped)
// IOrderRepository -> OrderRepository (Scoped)
// IProductRepository -> ProductRepository (Scoped)
Discover and analyze types in your application:
using Myth.ValueProviders;
// Get base namespace
var baseNamespace = TypeProvider.BaseApplicationNamespace;
// Returns: "MyApp" (from MyApp.Domain, MyApp.Services, etc.)
// Get all application assemblies
var assemblies = TypeProvider.ApplicationAssemblies;
// Returns: All loaded assemblies from your application
// Get all concrete types
var types = TypeProvider.ApplicationTypes;
// Returns: All non-abstract, non-interface types
// Find types implementing specific interface
var handlers = TypeProvider.GetTypesAssignableFrom<ICommandHandler>();
// Returns: All types implementing ICommandHandler
Eliminate manual repository registration:
// Before: Manual registration for each repository
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IProductRepository, ProductRepository>();
// ... dozens more
// After: One-line registration
services.AddServiceFromType<IRepository>();
Convention: The implementation class name must contain the interface name.
IUserRepository -> UserRepository (valid)IOrderRepository -> OrderRepository (valid)IProductRepository -> ProductRepositoryImpl (valid)Automatically register command and query handlers:
// Register all command handlers
services.AddServiceFromType<ICommandHandler>(ServiceLifetime.Transient);
// Register all query handlers
services.AddServiceFromType<IQueryHandler>(ServiceLifetime.Transient);
// Register all event handlers
services.AddServiceFromType<IEventHandler>(ServiceLifetime.Scoped);
Dynamically discover and load plugins:
// Find all plugin types
var pluginTypes = TypeProvider.GetTypesAssignableFrom<IPlugin>();
foreach (var pluginType in pluginTypes) {
var plugin = (IPlugin)Activator.CreateInstance(pluginType);
plugin.Initialize();
Console.WriteLine($"Loaded plugin: {plugin.Name}");
}
Specify service lifetime when registering:
// Register as Singleton
services.AddServiceFromType<ICache>(ServiceLifetime.Singleton);
// Register as Transient
services.AddServiceFromType<IValidator>(ServiceLifetime.Transient);
// Register as Scoped (default)
services.AddServiceFromType<IUnitOfWork>(ServiceLifetime.Scoped);
Auto-register domain services and repositories:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// Infrastructure layer
services.AddServiceFromType<IRepository>(ServiceLifetime.Scoped);
// Domain services
services.AddServiceFromType<IDomainService>(ServiceLifetime.Scoped);
// Application services
services.AddServiceFromType<IApplicationService>(ServiceLifetime.Scoped);
}
}
Generate documentation about your application structure:
using Myth.ValueProviders;
public class AssemblyAnalyzer {
public void PrintApplicationStructure() {
Console.WriteLine($"Base Namespace: {TypeProvider.BaseApplicationNamespace}");
Console.WriteLine("\nAssemblies:");
foreach (var assembly in TypeProvider.ApplicationAssemblies) {
Console.WriteLine($" - {assembly.GetName().Name} v{assembly.GetName().Version}");
}
Console.WriteLine("\nService Implementations:");
var services = TypeProvider.GetTypesAssignableFrom<IService>();
foreach (var service in services) {
Console.WriteLine($" - {service.FullName}");
}
}
}
Static class for type and assembly discovery.
// Gets the first part of your application namespace
public static string? BaseApplicationNamespace { get; }
// Gets all assemblies from your application
public static IEnumerable<Assembly> ApplicationAssemblies { get; }
// Gets all concrete types exported by your application
public static IEnumerable<Type> ApplicationTypes { get; }
// Gets types derived from or implementing the specified type
public static IEnumerable<Type> GetTypesAssignableFrom<TType>()
Examples:
// Get base namespace
var ns = TypeProvider.BaseApplicationNamespace;
// "MyCompany" from "MyCompany.ECommerce.Domain"
// Get all assemblies
var assemblies = TypeProvider.ApplicationAssemblies;
// Get all types
var allTypes = TypeProvider.ApplicationTypes;
// Get types implementing interface
var repositories = TypeProvider.GetTypesAssignableFrom<IRepository>();
var handlers = TypeProvider.GetTypesAssignableFrom<IHandler>();
var validators = TypeProvider.GetTypesAssignableFrom<IValidator>();
Extension methods for IServiceCollection.
// Adds all implementations of the specified type to the service collection
public static IServiceCollection AddServiceFromType<TType>(
this IServiceCollection services,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped
)
Parameters:
TType: The base interface or type to search for implementationsserviceLifetime: The service lifetime (Scoped, Transient, or Singleton). Default: ScopedReturns: The service collection for chaining
Throws:
InterfaceNotFoundException: When an implementation doesn't have a corresponding interfaceNaming Convention: The method finds implementations by matching the interface name within the implementation name.
IPersonRepository -> Implementation: PersonRepository (match: "PersonRepository")IOrderService -> Implementation: OrderService (match: "OrderService")Examples:
// Scoped (default)
services.AddServiceFromType<IRepository>();
// Transient
services.AddServiceFromType<IValidator>(ServiceLifetime.Transient);
// Singleton
services.AddServiceFromType<ICache>(ServiceLifetime.Singleton);
Structure your DI registration by architectural layer:
public void ConfigureServices(IServiceCollection services) {
// Persistence Layer
services.AddServiceFromType<IRepository>(ServiceLifetime.Scoped);
// Domain Layer
services.AddServiceFromType<IDomainService>(ServiceLifetime.Scoped);
// Application Layer
services.AddServiceFromType<IApplicationService>(ServiceLifetime.Scoped);
// Infrastructure Layer
services.AddServiceFromType<IExternalService>(ServiceLifetime.Transient);
}
Create marker interfaces for auto-registration:
// Marker interface for domain services
public interface IDomainService { }
public interface IOrderService : IDomainService {
Task<Order> CreateOrderAsync(CreateOrderCommand command);
}
public class OrderService : IOrderService {
public async Task<Order> CreateOrderAsync(CreateOrderCommand command) {
// Implementation
}
}
// Auto-register all domain services
services.AddServiceFromType<IDomainService>();
Ensure consistent naming for automatic discovery:
// Good: Name matches interface
public interface IUserRepository { }
public class UserRepository : IUserRepository { }
// Good: Implementation name contains interface name
public interface IProductRepository { }
public class ProductRepositoryImpl : IProductRepository { }
// Bad: No naming correlation
public interface IOrderRepository { }
public class OrderDataAccess : IOrderRepository { } // Won't be auto-discovered
Use auto-registration for conventions, manual for exceptions:
// Auto-register most services
services.AddServiceFromType<IRepository>();
services.AddServiceFromType<IService>();
// Manually register special cases
services.AddSingleton<IConfiguration>(configuration);
services.AddScoped<ICurrentUser, CurrentUserAccessor>();
services.AddTransient<IEmailSender, SendGridEmailSender>();
Check that types were registered correctly:
var serviceProvider = services.BuildServiceProvider();
// Verify critical services are registered
var userRepo = serviceProvider.GetService<IUserRepository>();
if (userRepo == null) {
throw new InvalidOperationException("IUserRepository not registered");
}
// Or use GetRequiredService to throw if not found
var orderService = serviceProvider.GetRequiredService<IOrderService>();
AppDomain.CurrentDomain.BaseDirectory for all .dll filesServiceDescriptor and adds to the service collectionInterfaceNotFoundException if no matching interface is foundApplicationAssemblies access; results are not cached between accessesMyth.DependencyInjection works seamlessly with other Myth libraries:
using Myth.Extensions;
using Myth.ValueProviders;
var builder = WebApplication.CreateBuilder(args);
// Auto-register repositories (Myth.Repository)
builder.Services.AddServiceFromType<IRepository>();
// Add Flow pipeline support (Myth.Flow)
builder.Services.AddFlow();
// Add Guard validation (Myth.Guard)
builder.Services.AddGuard();
// Build with global provider support
var app = builder.BuildApp();
app.Run();
Problem: InterfaceNotFoundException: Not found a interface that corresponds to type
Solution: Ensure your implementation class name contains the interface name:
// Problematic
public interface IUserRepository { }
public class UserDataAccess : IUserRepository { } // Name doesn't contain "IUserRepository"
// Fixed
public interface IUserRepository { }
public class UserRepository : IUserRepository { } // Contains "UserRepository"
Problem: Expected types are not found by GetTypesAssignableFrom<T>()
Solution:
Problem: A class implements multiple interfaces
Solution: The auto-registration finds the first interface containing the class name. For precise control, register manually:
// Auto-registration picks one interface
services.AddServiceFromType<IService>();
// Manual registration for multiple interfaces
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IReadOnlyUserRepository, UserRepository>();
This project is licensed under the Apache 2.0 License - see the LICENSE for details.
Contributions are welcome! Please feel free to submit a Pull Request.