Addtransient, Addscoped and Addsingleton Services Differences

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR

Transient objects are always different; a new instance is provided to
every controller and every service.

Scoped objects are the same within a request, but different across
different requests.

Singleton objects are the same for every object and every request.

For more clarification, this example from .NET documentation shows the difference:

To demonstrate the difference between these lifetime and registration options, consider a simple interface that represents one or more tasks as an operation with a unique identifier, OperationId. Depending on how we configure the lifetime for this service, the container will provide either the same or different instances of the service to the requesting class. To make it clear which lifetime is being requested, we will create one type per lifetime option:

using System;

namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}

public interface IOperationTransient : IOperation
{
}

public interface IOperationScoped : IOperation
{
}

public interface IOperationSingleton : IOperation
{
}

public interface IOperationSingletonInstance : IOperation
{
}
}

We implement these interfaces using a single class, Operation, that accepts a GUID in its constructor, or uses a new GUID if none is provided:

using System;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Classes
{
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance
{
Guid _guid;
public Operation() : this(Guid.NewGuid())
{

}

public Operation(Guid guid)
{
_guid = guid;
}

public Guid OperationId => _guid;
}
}

Next, in ConfigureServices, each type is added to the container according to its named lifetime:

services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

Note that the IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty, so it will be clear when this type is in use. We have also registered an OperationService that depends on each of the other Operation types, so that it will be clear within a request whether this service is getting the same instance as the controller, or a new one, for each operation type. All this service does is expose its dependencies as properties, so they can be displayed in the view.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
public class OperationService
{
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }

public OperationService(IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
}
}

To demonstrate the object lifetimes within and between separate individual requests to the application, the sample includes an OperationsController that requests each kind of IOperation type as well as an OperationService. The Index action then displays all of the controller’s and service’s OperationId values.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
public class OperationsController : Controller
{
private readonly OperationService _operationService;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationSingletonInstance _singletonInstanceOperation;

public OperationsController(OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_operationService = operationService;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
_singletonInstanceOperation = singletonInstanceOperation;
}

public IActionResult Index()
{
// ViewBag contains controller-requested services
ViewBag.Transient = _transientOperation;
ViewBag.Scoped = _scopedOperation;
ViewBag.Singleton = _singletonOperation;
ViewBag.SingletonInstance = _singletonInstanceOperation;

// Operation service has its own requested services
ViewBag.Service = _operationService;
return View();
}
}
}

Now two separate requests are made to this controller action:

First Request

Second Request

Observe which of the OperationId values varies within a request, and between requests.

  • Transient objects are always different; a new instance is provided to every controller and every service.

  • Scoped objects are the same within a request, but different across different requests

  • Singleton objects are the same for every object and every request (regardless of whether an instance is provided in ConfigureServices)

Why use AddScoped() instead of AddSingleton()?

As you said, you know the difference so I won't get into that.

The reason you don't want addSingleton for your repositories or services is because typically your repositories and services are considered "business logic" and "persistence logic". And in your business logic you might have some class level variables that are getting set. Those properties would not be different for every request, they would be shared across the requests. (think of them like static properties).

Example:

Imagine you have a user service that sets the username of the user making the request as a class level variable.

Singleton logic:

Now imagine Bob makes a request to the api. The username would be set to "Bob" . Now imagine at the same time, John makes a request to the api. The username would get set to "John". But because the user service is a singleton, both John and Bob are sharing the same instance, meaning Bob's username would also be set to "John".

Scoped logic:

Imagine the exact same scenario as above, but this time when John makes a request, it does not override bobs username, because they are different instances.

When to use following Transient, scoped and singleton

As far as I know, the Singleton is normally used for a global single instance. For example, you will have an image store service you could have a service to load images from a given location and keeps them in memory for future use.

A scoped lifetime indicates that services are created once per client request. Normally we will use this for sql connection. It means it will create and dispose the sql connection per request.

A transient lifetime services are created each time they're requested from the service container. For example, during one request you use httpclient service to call other web api request multiple times, but the web api endpoint is different. At that time you will register the httpclient service as transient. That means each time when you call the httpclient service it will create a new httpclient to send the request not used the same one .

What are the practical scenarios to use IServiceCollection.AddTransient, IServiceCollection.AddSingleton and IServiceCollectionAddScoped Methods?

Your understanding of all 3 scopes is correct.

Transient would be used when the component cannot be shared. A non-thread-safe database access object would be one example.

Scoped can be used for Entity Framework database contexts. The main reason is that then entities gotten from the database will be attached to the same context that all components in the request see. Of course if you plan on doing queries with it in parallel, you can't use Scoped.

Another example of a Scoped object would be some kind of a RequestContext class, that contains e.g. the username of the caller. A middleware/MVC filter can request it and fill out the info, and other components down the line can also request for it, and it will surely contain the info for the current request.

Singleton components are shared always, so they are best for thread-safe components that do not need to be bound to a request. An example would be IOptions, which gives access to configuration settings. An HttpClient wrapper class that uses SendAsync on a single static HttpClient instance would also be completely thread-safe, and a good candidate for being a Singleton.

Note that if you have a Singleton component that depends on a Scoped component, its dependency would get disposed before it. Thus a component cannot depend on another component that has smaller scope than itself.

For AddScoped(), what is meant by within the same HTTP request

So, there's always a "root container" that's shared by all consumers in the AppDomain or running process. Child containers are then created for each HTTP request (in ASP.NET Core, for each HttpContext which encompasses HttpRequest and HttpResponse). (Note that child containers can be created for other reasons too, but that's outside this answer's concern).

  • Singleton services are only constructed once, usually only by the root container. They're like the Singleton-pattern in OOP (where a class can only be instantiated once), except in this case you can still manually create multiple instances, but the DI container will only ever create 1 instance itself.

    • You can use OOP Singletons with DI containers by returning the OOP singleton instance from a service factory method.
  • Transient services are always created when they're requested - they're meant to be short-lived services. Some containers will call IDisposable.Dispose on all transient services it creates, others will not (as they expect the consumer to dispose of them, check with your container's policies).

  • Request-scoped services can be implemented differently by different container systems - but a common approach I see is that at the start of each HTTP request (when a new HttpContext is created) a child-container is created (a child-container inherits the registrations of its parent) and then all of the objects it creates (often as singletons, but only in that child container) are then disposed (if applicable) when the HTTP request ends (when the HttpContext is destroyed, after the HTTP response has been sent to the client and the response ended).

Disregarding ASP.NET entirely - let's pretend we have our own HTTP server program with its own DI container:

public class HttpServer
{
private readonly IContainer rootContainer;

public HttpServer()
{
this.rootContainer = RegisterServices( new ContainerBuilder() ).Build();

}

private static IContainerBuilder RegisterServices( IContainerBuilder services )
{
return services
.RegisterSingleton<ISystemClock,BiosClock>()
.RegisterSingleton<MySingleton>( factory: () => MySingleton.Instance )
.RegisterTransient<IDbConnection>( factory: () => new SqlConnection() )
.RegisterRequest<RequestTracingService>();
}

public void OnHttpRequest( Socket socket )
{
HttpContext context = new HttpContext();
context.RequestContainer = this.rootContainer.CreateChildContainer();

try
{
// hand-off the `context` object to code that reads the request, does processing, and then writes the response
}
finally
{
context.RequestContainer.Dispose(); // <-- this disposes of any objects created by RequestContainer during the processing of the request, without touching any objects created by `rootContainer`.
}
}
}

Asp.Net Core: should I use AddScoped or AddSingleton on a cache service

When you register the Memory cache with services.AddMemoryCache() it's added as a Singleton, so it would seem logical to register your service as a Singleton as well.

Source code

MemoryCache is also thread safe so there should be no reason to add a lock. If multiple calls to Set() are made for a given key, it will simply update the exist value.

MemoryCache Docs

How approach is better to register services Dependency Injection in .Net Core

Use AddScoped . Scoped services are created per scope. In a web application, every web request creates a new separated service scope. That means scoped services are generally created per web request.

If you're calling db multiple times in the same request, then use scoped lifetime will help keeping the same repository object in the memory and reuses that multiple times within the same Http Request context. If using transient , it will creates a new repository object multiple times and consumes more memory .



Related Topics



Leave a reply



Submit