.NET Dependency Injection: IServiceCollection, IServiceProvider, Key-Typed Services, and IEnumerable Services
Decoding the Essentials of. NET’s Dependency Injection
Dependency Injection (DI) is a cornerstone of modern .NET development, enabling cleaner, more testable, and maintainable applications. If you’ve worked with DI in .NET, you’ve likely encountered concepts like IServiceCollection
, IServiceProvider
, key-typed services, and IEnumerable<T>
services.
In this blog, we’ll demystify these concepts, explore their roles in DI, and see them in action through practical examples.
📌Explore more at: https://dotnet-fullstack-dev.blogspot.com/
🌟 Clapping would be appreciated! 🚀
What is IServiceCollection
?
IServiceCollection
is a central part of the .NET DI system. It is:
- A Container: Used to register services and their lifetimes (e.g., Singleton, Scoped, Transient).
- An Abstraction: A collection of service descriptors that define how services are resolved.
How Does It Work?
When you register services in a .NET application (typically in Startup.cs
or Program.cs
), you’re interacting with an IServiceCollection
. It defines what services are available for injection and their lifetimes.
Example:
var services = new ServiceCollection();
services.AddSingleton<IMyService, MyService>();
services.AddTransient<IAnotherService, AnotherService>();
Common Methods of IServiceCollection
AddSingleton<TService, TImplementation>()
: A single instance is shared across the application.AddScoped<TService, TImplementation>()
: A new instance is created for each scope (e.g., HTTP request in ASP.NET Core).AddTransient<TService, TImplementation>()
: A new instance is created each time it is requested.
What is IServiceProvider
?
IServiceProvider
is the resolver in the DI system:
- It is used to retrieve services registered in the
IServiceCollection
. - Acts as the runtime engine that delivers dependencies to your application.
How Does It Work?
When the application runs, the IServiceProvider
resolves services based on the registrations in IServiceCollection
.
Example:
var serviceProvider = services.BuildServiceProvider();
var myService = serviceProvider.GetService<IMyService>();
BuildServiceProvider()
: Converts theIServiceCollection
into anIServiceProvider
.GetService<T>()
: Resolves the service of typeT
.
Note: In most cases, you don’t need to manually call
GetService<T>()
because DI automatically resolves dependencies.
Key-Typed Services
Key-typed services allow you to register multiple implementations of the same service interface and differentiate between them by a key. This is useful when you need different behaviors from the same interface.
Example: Registering Key-Typed Services
Let’s say you have a logging service with different implementations:
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"FileLogger: {message}");
}
public class DatabaseLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"DatabaseLogger: {message}");
}
You can register them with keys:
services.AddSingleton<ILogger, FileLogger>("File");
services.AddSingleton<ILogger, DatabaseLogger>("Database");
Resolving Key-Typed Services
Use a factory or a wrapper to resolve services based on a key.
public class LoggerFactory
{
private readonly IServiceProvider _provider;
public LoggerFactory(IServiceProvider provider)
{
_provider = provider;
}
public ILogger GetLogger(string key)
{
return key switch
{
"File" => _provider.GetService<FileLogger>(),
"Database" => _provider.GetService<DatabaseLogger>(),
_ => throw new InvalidOperationException("Invalid logger key.")
};
}
}
IEnumerable<T> Services
Sometimes, you may need all registered implementations of a service. The DI container in .NET supports injecting IEnumerable<T>
, which gives you all services registered for a given type.
Example: Multiple Implementations
Using the same ILogger
example:
services.AddSingleton<ILogger, FileLogger>();
services.AddSingleton<ILogger, DatabaseLogger>();
Inject IEnumerable<ILogger>
to get all implementations:
public class LoggingService
{
private readonly IEnumerable<ILogger> _loggers;
public LoggingService(IEnumerable<ILogger> loggers)
{
_loggers = loggers;
}
public void LogToAll(string message)
{
foreach (var logger in _loggers)
{
logger.Log(message);
}
}
}
When to Use IEnumerable<T>
- Aggregating multiple implementations of a service.
- Executing tasks in parallel or sequence using all services of a type.
How These Fit Together
Lifecycle of Dependency Injection in .NET
- Register Services: Use
IServiceCollection
to register dependencies. - Build Provider: The DI container generates an
IServiceProvider
. - Resolve Dependencies: Use the
IServiceProvider
(or constructor injection) to resolve services. - Handle Multiple Implementations:
- Use
Key-Typed Services
for differentiated behaviors. - Use
IEnumerable<T>
for aggregating all implementations.
Key Differences and Scenarios
Conclusion
Understanding the interplay between IServiceCollection
, IServiceProvider
, key-typed services, and IEnumerable<T>
services is crucial for mastering Dependency Injection in .NET. These features enable you to design flexible, maintainable, and testable applications by:
- Structuring dependencies effectively.
- Supporting multiple implementations and usage patterns.
With these concepts, you can fully leverage .NET’s DI system to build scalable and robust applications. 🚀 Happy coding! 😊