Skip to content

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.

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

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., IUserServiceUserService)
  • 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 { }

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 System or Microsoft namespaces
  • 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 attribute
public class BadService(string connectionString) { } // Primitive parameter

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

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 AddBindingsByConvention and AddSelfBindings

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>();
}

Ensure your interfaces and implementations follow the expected patterns:

// ✅ Good - follows convention
public interface IUserRepository { }
public class UserRepository : IUserRepository { }
// ❌ Bad - doesn't follow convention
public interface UserRepo { }
public class UserRepositoryImpl : UserRepo { }

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 { }

Constructor parameters should use concepts or dependency injection:

// ❌ Bad - primitive parameters prevent auto-registration
public class EmailService(string smtpServer, int port) { }
// ✅ Good - uses concepts and injection
public class EmailService(SmtpConfiguration config, ILogger<EmailService> logger) { }

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.