.NET Dependency Injection: IServiceCollection, IServiceProvider, Key-Typed Services, and IEnumerable Services

Decoding the Essentials of. NET’s Dependency Injection

DotNet Full Stack Dev
3 min readNov 30, 2024

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

  1. AddSingleton<TService, TImplementation>(): A single instance is shared across the application.
  2. AddScoped<TService, TImplementation>(): A new instance is created for each scope (e.g., HTTP request in ASP.NET Core).
  3. 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 the IServiceCollection into an IServiceProvider.
  • GetService<T>(): Resolves the service of type T.

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

  1. Register Services: Use IServiceCollection to register dependencies.
  2. Build Provider: The DI container generates an IServiceProvider.
  3. Resolve Dependencies: Use the IServiceProvider (or constructor injection) to resolve services.
  4. 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! 😊

--

--

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

No responses yet