Dependency Injection
The Cratis.DependencyInjection namespace provides convention-based dependency injection features that simplify service registration in .NET applications. These utilities help reduce boilerplate code when working with Microsoft’s dependency injection container.
Overview
Section titled “Overview”The dependency injection utilities offer:
- Convention-based registration: Automatically register services following naming conventions
- Self-binding: Register concrete types for direct injection
- Lifecycle management: Control service lifetimes using attributes
- Selective registration: Exclude types from convention-based registration
Extension Methods
Section titled “Extension Methods”AddBindingsByConvention
Section titled “AddBindingsByConvention”Automatically registers services based on interface naming conventions.
services.AddBindingsByConvention();Convention Rules:
- Interface and implementation must be in the same namespace
- Interface name must be
I{ImplementationName}(e.g.,IUserService→UserService) - Only one implementation per interface is allowed
- Types with
[IgnoreConvention]attribute are excluded
Examples:
// These will be automatically registered:public interface IUserService { }public class UserService : IUserService { } // Registered as Transient
public interface IOrderProcessor { }[Singleton]public class OrderProcessor : IOrderProcessor { } // Registered as Singleton
// This will be ignored:[IgnoreConvention]public class TestUserService : IUserService { }AddSelfBindings
Section titled “AddSelfBindings”Registers concrete types for direct injection without requiring an interface.
services.AddSelfBindings();Registration Rules:
- Non-static, non-abstract, non-interface types
- Types not derived from
Exception - Types without
[IgnoreConvention]attribute - Types not in
SystemorMicrosoftnamespaces - Types with resolvable constructor parameters
Examples:
// These will be automatically registered:public class EmailSender { } // Registered as Transient
[Singleton]public class CacheManager { } // Registered as Singleton
// These will be ignored:public class SystemType { } // In System namespace[IgnoreConvention]public class InternalService { } // Has ignore attributepublic class BadService(string connectionString) { } // Primitive parameterAttributes
Section titled “Attributes”SingletonAttribute
Section titled “SingletonAttribute”Marks a class to be registered as a singleton service.
[Singleton]public class DatabaseConnection : IDatabaseConnection{ // This service will be registered as a singleton}Usage:
- Apply to class declarations
- Works with both convention-based and self-binding registration
- Overrides the default transient lifetime
IgnoreConventionAttribute
Section titled “IgnoreConventionAttribute”Excludes a class from convention-based registration.
[IgnoreConvention]public class TestImplementation : IService{ // This class will not be automatically registered}Usage:
- Apply to class declarations
- Useful for test implementations, decorators, or manually registered services
- Affects both
AddBindingsByConventionandAddSelfBindings
Best Practices
Section titled “Best Practices”1. Use Convention-Based Registration First
Section titled “1. Use Convention-Based Registration First”Start with convention-based registration for most services:
public void ConfigureServices(IServiceCollection services){ services.AddBindingsByConvention(); services.AddSelfBindings();
// Manual registrations for special cases services.AddScoped<ISpecialService, CustomImplementation>();}2. Follow Naming Conventions
Section titled “2. Follow Naming Conventions”Ensure your interfaces and implementations follow the expected patterns:
// ✅ Good - follows conventionpublic interface IUserRepository { }public class UserRepository : IUserRepository { }
// ❌ Bad - doesn't follow conventionpublic interface UserRepo { }public class UserRepositoryImpl : UserRepo { }3. Use Attributes Appropriately
Section titled “3. Use Attributes Appropriately”Apply lifecycle and exclusion attributes where needed:
// Singleton for expensive-to-create services[Singleton]public class DatabaseConnectionPool : IDatabaseConnectionPool { }
// Ignore for test doubles or special implementations[IgnoreConvention]public class MockUserService : IUserService { }4. Avoid Primitive Dependencies
Section titled “4. Avoid Primitive Dependencies”Constructor parameters should use concepts or dependency injection:
// ❌ Bad - primitive parameters prevent auto-registrationpublic class EmailService(string smtpServer, int port) { }
// ✅ Good - uses concepts and injectionpublic class EmailService(SmtpConfiguration config, ILogger<EmailService> logger) { }Integration Example
Section titled “Integration Example”Complete setup in a typical ASP.NET Core application:
public class Startup{ public void ConfigureServices(IServiceCollection services) { // Add convention-based services services.AddBindingsByConvention(); services.AddSelfBindings();
// Add framework services services.AddControllers(); services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connectionString));
// Manual registrations for complex scenarios services.AddScoped<IEmailService>(provider => new EmailService(provider.GetRequiredService<IConfiguration>())); }}This approach significantly reduces the amount of manual service registration code while maintaining flexibility for special cases.