AddTransient vs AddScoped vs AddSingleton

Dependency Injections (DI) are one of the principal concepts of ASP.NET core. It is a software design pattern that makes the code easily maintainable by reducing tight coupling and in turn allowing loose coupling between the various software components. Injecting these dependencies at run time instead of design time helps to reduce hard coded dependencies among the classes.

Implementation of Dependency Injections

Dependency Injection is implemented by configuring a Dependency Injection (DI) container in the ConfigureServices() method of Startup.cs class file. The DI container either returns a new or an existing instance of the service.

Shown below is the syntax of Dependency Injection in Startup.cs file :

 public void ConfigureServices(IServiceCollection services) 
 { 
   //Transient Service called using AddTransient()
   services.AddTransient<ITransientService, LifeCycleTest>(); 
   
   //Scoped Service called using AddScoped()
   services.AddScoped<IScopedService, LifeCycleTest>(); 
   
   //Singleton Service called using AddSingleton()
   services.AddSingleton<ISingletonService, LifeCycleTest>(); 
 }

Dependency Injection Container offers 3 methods for registration of services. They are AddSingleton(), AddScoped() and AddTransient(). Let us look at each one of these in detail to understand better.

Singleton and AddSingleton()

A singleton service creates an instance only one time. When the singleton service is requested for the first time, it uses the same instance for all its requests throughout the entire span of the application. The method that is used to call singleton service is known as AddSingleton().

Syntax : services.AddSingleton()

Advantage : Due to reusability of singleton services throughout the application, they are efficient in memory.

Disadvantage :  Higher chances of memory leakage over time due to reusability.

When to use: Singleton services can be used when you want to maintain the same state of data, parameters, configurations etc. throughout the application.

Scoped and AddScoped()

A scoped service creates an instance a single time for each scope. It is similar to a singleton service but the difference is that one service requests lasts only for that very scoped request and not for the entire application. The method that is used to call scoped service is known as AddScoped().

Syntax : services.AddScoped()

Advantage : Used in case of Entity Framework for databases because the entities, parameters etc. that are extracted from the database should be able to maintain the state within the current scope.

Disadvantage :  If queries are to be performed in parallel, scoped cannot be used.

When to use: Scoped services can be used in applications which have different behaviour per user. It can also be used when you want to maintain the same state of data, parameters, configurations etc. within the scope.

Transient and AddTransient()

A transient service creates a new instance of the request each and every time even if the request is same. The method that is used to call transient service is known as AddTransient().

Syntax : services.AddTransient()

Advantage : Transient services are the safest and the most commonly used.

Disadvantage :  Due to creation of services each time, more usage of memory and resources occurs which affects the performance.  

When to use: Transient services can be used for light-weighted services with few or no states. It is also the go-to option in case multi-threading is implemented.

Example

Let us now look at an example and implement all 3 services on it.

Consider the following class with properties itemid, itemname, itemdetails and itemcost that describe the object Fooditems.

public class Fooditems
{
   public int itemid { get; set; }
   public string itemname { get; set; }
   public string itemdetails { get; set; }
   public string itemcost { get; set; }
}

Now we create IFooditemsService interface that contains :

GetAll() method which gets all the food items available.

Add() method which adds a new food item.

public interface IFooditemsService
{
    IEnumerable<Fooditems> GetAll();   
    Fooditems Add(Fooditems fooditems);
}

IFooditemsService is implemented by FooditemsService. The _fooditemsList field stores the details of all the food items. This is followed by the definition of the 2 functions GetAll() and Add() while making use of the data stored in the _fooditemsList.

    public class FooditemsService:IFooditemsService
    {
        private List<Fooditems> _fooditemsList;

        public FooditemsService()
        {
            _fooditemsList = new List<Fooditems>()
            {
                new Fooditems() { itemid = 1, itemname = "Sandlewood", itemdetails = "From Kashmir", itemcost = "10,000"},
                new Fooditems() { itemid = 2, itemname = "Kesar", itemdetails = "From Shimla", itemcost = "20,000" },
                new Fooditems() { itemid = 3, itemname = "Cashewnuts", itemdetails = "From Goa", itemcost = "30,000" }
            };
        }

        public Fooditems Add(Fooditems fooditems)
        {
            fooditems.itemid = _fooditemsList.Max(e => e.itemid) + 1;
            _fooditemsList.Add(fooditems);
            return fooditems;
        }

        public IEnumerable<Fooditems> GetAll()
        {
            return _fooditemsList;
        }
    }

IFooditemsService is then injected in the HomeController. The POST request in the Create() method of HomeController adds fooditems instance to the database.

    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private IFooditemsService _fooditemsService;
        public HomeController (ILogger <HomeController> logger, IFooditemsService fooditemsService )
        {
            _logger = logger;
            _fooditemsService = fooditemsService;
        }

        [HttpGet]
        public ViewResult Create ()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create ( Fooditems fooditems )
        {
            if (ModelState.IsValid)
            {
                Fooditems newFooditems = _fooditemsService.Add(fooditems);
            }
            return View();
        }
    }

Now we create a view for the Create() method and inject IFooditemsService into it to display the total number of food items and their details using @inject directive.

@model Fooditems
@inject IFooditemsService fooditemsdata
@{
    ViewData["Title"] = "Create";
}
    <form asp-controller = "home" asp-action="create" method="post">
        <div>
            <label asp-for = "itemname"></label>
            <div>
                <input asp-for = "itemname">
            </div>
        </div>
        <div>
            <label asp-for = ”itemdetails"></label>
            <div>
                <input asp-for = ”itemdetails">
            </div>
        </div>
        <div>
            <label asp-for = "itemcost"></label>
            <div>
                <input asp-for = "itemcost">
            </div>
        </div>

        <div>
            <button type = "submit">Create</button>
        </div>

        <div>
            Total Employees Count = @fooditemsdata.GetAll().Count().ToString()
        </div>
    </form>

Implementation of the dependency container of AddSingleton ()

   public void ConfigureServices ( IServiceCollection services )
   {
       services.AddControllersWithViews ();
       services.AddMvc ();
       services.AddSingleton < IFooditemsService, FooditemsService > ();
   }

In AddSingleton, every time we add a new food item and click on create button, the value of count will go on incrementing. The count value will not reset even on refreshing the page and will continue incrementing from the previous value.

Implementation of the dependency container of AddScoped ()

    public void ConfigureServices ( IServiceCollection services )
    {
        services.AddControllersWithViews();
        services.AddMvc();
        services.AddScoped<IFooditemsService, FooditemsService>();
    }

In AddScoped, when we add a new food item and click on create button, the value of count will increment for the first time but not after that. This is because a new HTTP request is issued every time we click on the create button. Each time the page is refreshed, a new instance is created and hence the value is reset. The instance remains the same in cases when the services is required at multiple places such as view and controller but within the same scope.

Implementation of the dependency container of AddTransient()

    public void ConfigureServices ( IServiceCollection services )
    {
        services.AddControllersWithViews ();
        services.AddMvc ();
        services.AddTransient < IFooditemsService, FooditemsService > ();
    }

In AddTransient, the count value will never increment because every time you click on the create button, POST method is requested and for each post request, a new transient service instance is created. A new instance gets created every time irrespective of whether it is for the same HTTP request or for a different one.

Conclusion

Let us summarize by comparing the main differentiating factors of all 3 services together.

AddSingleton ()AddScoped ()AddTransient()
Same instance is used for every request as well as for every user.One instance is used per request.Different instance each and every time even when there are multiple same requests.
Least commonly used.Used mainly for Database Entity Framework purposes.Most commonly used.
Uses least amount of memory.Uses more memory than singleton and less memory than transient.Uses most amount of memory
Worst option security wise.Moderately good option security wise.Best option security wise.
Instance is discarded when the app is shut.Instance is discarded at the end of request.Instance is discarded at the end of request.

Leave a Comment

Your email address will not be published.