Dependency Injection In .NET – Basic Terminology

Dependency Injection In .NET – Basic Terminology

In last post, we discussed a bit about dependency inversion principle and how it is implemented in .NET. In this post, let’s discuss a bit more about service lifetimes and some other basic terms.

Dependency, Service and Client

There are three important words which we may use a lot while talking about dependency injections, viz, dependency, service and client.

A dependency is any object that another object depends upon. So, if you have a Repository class, which uses SqlDataReader to read the data from database, then SqlDataReader is a dependency for Repository class.

The object which receives dependency is called as client, while the object which is passed is called as service. In above example, Repository is client and SqlDataReqder is the service being passed to the client.

How does DI work ?

We already have seen what is a service in the context of dependency injection. These services are added and configured in IServiceCollection implementation. The IHost exposes IServiceProvider instance and it acts as a container.

This means, in any application, where you want to use the default dependency injection mechanism provided by .NET, you will have to create the IHost instance.

The IServiceProvider has responsibility to maintain the pool objects, create new if required or dispose the objects which are not required anymore. It takes the decisions based on the lifetimes configured in IServiceCollection.

Service Lifetimes

As the name suggests, service lifetimes decide when a new object should be created and when an existing object should be used for injecting the dependencies. It also decides when/how the objects would be disposed. There are three different lifetimes, Transient, Scoped and Singleton. Let’s try to know a bit more about them.

Transient

Every time a transient dependency is requested, the container provides new instance of the dependency. This might be best suitable for stateless objects.

We can register transient services with AddTransient call.

Scoped

This service lifetime is basically important for the web applications.

A scope basically starts when a client request is received by a web app and it ends when the request processing is completed.

We can register scoped services with AddScoped. The scoped objects are created when the client request is received and disposed when the request processing is complete.

A scoped service cannot be resolved by a Singleton service (either directly or indirectly).

Singleton

Every time a singleton dependency is requested, the container provides same object. So if we register any dependency as Singleton, then it means there will be always 1 and only 1 object, which will be passed to all the clients requesting that object.

There are two ways in which Singleton objects are created:

  • first time it is requested
  • or the developer can decide to create the object and pass that object to the container. Practically, this is almost never required.

We can register singleton services with AddSingleton call. We should ensure that the Singleton services are thread safe. We should not implement singleton design pattern as container can take care of managing the lifetime of singleton object.

The singleton objects are disposed when the application is shutdown, because IServiceProvider is disposed only after application is shutdown.

Resolving Dependencies

Once the dependencies are registered in the container (which typically happens during application bootstrap), they can be used by other objects. For using the dependency, the client object can specify the dependencies in their constructors.

Constructors can also have mix arguments some can be resolved by dependency injection while others might not be provided by DI container. Such dependencies which cannot be provided dependency injection container, should have the default values specified.

Below code snippet from .NET Core MVC web application shows ConfigureServices method registering dependencies and how those dependencies are used by HomeController.

// Startup.cs
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICustomLogger, CustomLogger>();
services.AddScoped<IRepository, Repository>();
services.AddControllersWithViews();
}
}
// HomeController.cs
public class HomeController : Controller
{
private readonly ICustomLogger _logger;
private readonly IRepository _repository;
public HomeController(ICustomLogger logger, IRepository repository)
{
_logger = logger;
_repository = repository;
}
public IActionResult Index()
{
// Load data from repository
ViewData["DataCollection"] = repository.GetData();
_logger.Log("data retrieved successfully");
return View();
}
}
view raw BasicTerminology.cs hosted with ❤ by GitHub

I hope this information is useful. Let me know your thoughts.

Leave a Reply