DependencyInjection
DependencyInjection
ASP.NET Core Dependency Injection (DI) is a powerful feature that helps manage
object dependencies within our application. DI is a design pattern used to achieve
loose coupling in software development. Instead of creating dependencies
(objects or services) directly within a class, dependencies are injected into the
class from outside, typically through constructor parameters.
Let us understand the need for Dependency Injection Design Patterns in ASP.NET
Core Applications with an example. First, create a new ASP.NET Core Application
named FirstCoreMVCWebApplication with an Empty Project Template. Once
you create the ASP.NET Core Empty Project, let us add our models. To do so, first,
create a folder with the name Models. Within the Models folder, let us add a class
file with the name Student.cs, and this Student class will be our model for this
application. Then open the Student.cs class file and copy and paste the following
code into it.
namespace FirstCoreMVCWebApplication.Models
{
public class Student
{
public int StudentId { get; set; }
public string? Name { get; set; }
public string? Branch { get; set; }
public string? Section { get; set; }
public string? Gender { get; set; }
}
}
using System.Collections.Generic;
namespace FirstCoreMVCWebApplication.Models
{
public interface IStudentRepository
1
{
Student GetStudentById(int StudentId);
List<Student> GetAllStudent();
}
}
Creating Service Implementation:
using System.Collections.Generic;
using System.Linq;
namespace FirstCoreMVCWebApplication.Models
{
public class StudentRepository : IStudentRepository
{
public List<Student> DataSource()
{
return new List<Student>()
{
new Student() { StudentId = 101, Name = "James", Branch = "CSE", Section =
"A", Gender = "Male" },
new Student() { StudentId = 102, Name = "Smith", Branch = "ETC", Section =
"B", Gender = "Male" },
new Student() { StudentId = 103, Name = "David", Branch = "CSE", Section =
"A", Gender = "Male" },
new Student() { StudentId = 104, Name = "Sara", Branch = "CSE", Section = "A",
Gender = "Female" },
new Student() { StudentId = 105, Name = "Pam", Branch = "ETC", Section = "B",
Gender = "Female" }
};
}
public Student GetStudentById(int StudentId)
{
return DataSource().FirstOrDefault(e => e.StudentId == StudentId) ?? new
Student();
}
public List<Student> GetAllStudent()
{
2
return DataSource();
}
}
}
Program.cs:
In the Program class, we need to do two things. First, we need to configure the
required MVC service to the IoC Container, and then we need to add the MVC
Middleware to the request processing pipeline. So, modify the Program class as
shown below.
namespace FirstCoreMVCWebApplication
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMvc();
var app = builder.Build();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
app.Run();
}
}
}
Without Dependency Injection:
Create a folder named Controllers in your project. Then, add a class file
named HomeController.cs within the Controllers folder and copy-paste the
following code. Here, you can see that within the Index and GetStudentDetails
action methods, we are creating an instance of the StudentRepository class and
calling the respective methods.
using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace FirstCoreMVCWebApplication.Controllers
{
public class HomeController : Controller
{
public JsonResult Index()
3
{
StudentRepository repository = new StudentRepository();
List<Student> allStudentDetails = repository.GetAllStudent();
return Json(allStudentDetails);
}
public JsonResult GetStudentDetails(int Id)
{
StudentRepository repository = new StudentRepository();
Student studentDetails = repository.GetStudentById(Id);
return Json(studentDetails);
}
}
}
With the above changes in place, now run the application and check the above
two methods. it should work as expected, as shown in the image below.
Let’s first understand the problem in the above implementation. Then, we will
discuss how to overcome it by using the Dependency Injection Design Pattern in
the ASP.NET Core application.
4
tightly coupled. We can overcome this problem by implementing the Dependency
Injection Design Pattern.
The ASP.NET Core Framework is designed from scratch to provide built-in support
for dependency injection design patterns. It injects dependency objects into a
class through a constructor or method using the built-in IoC (Inversion of
Control) container. The built-in IoC (Inversion of Control) container is
represented by IServiceProvider implementation, which supports default
constructor injection. The types (i.e., classes) managed by built-in IoC containers
are called services.
There are two types of services in ASP.NET Core. They are as follows:
To let the IoC container automatically inject our application services, we must first
register them with the IoC container.
ASP.NET Core provides 3 methods for registering a service with the ASP.NET Core
Dependency Injection container. The method we use to register a service will
determine its lifetime.
Singleton:
Services registered as Singleton are instantiated once per application and shared
throughout its lifetime. It is Ideal for configuration services, logging, or other
services where a single instance is sufficient and expensive to create. All requests
5
for the service within the application receive the same instance. The container
ensures thread safety when accessing the singleton instance. Exists for the entire
duration of the application. This can be achieved by adding the service as a
singleton through the AddSingleton method of the IServiceCollection.
Transient:
Services registered as Transient are created each time they are requested. This
method is suitable for lightweight, stateless services with short-lived and transient
behavior. A new instance is created every time the service is requested, and
instances are not shared among different consumers. This can be achieved by
adding the service through the AddTransient method of the IServiceCollection.
Scoped:
Services registered as Scoped are created once per request (in a web application,
this typically means per HTTP request). It is used for services that should be
reused within a single request, maintaining state across different components of
the request, but should not be shared across different requests. Instances are
disposed of at the end of the request. Common examples include data access
operations within a single transaction. This can be achieved by adding the service
through the AddScoped method of the IServiceCollection.
We need to register the service to the built-in dependency injection container with
the program class. The following code shows how to register a service with
different lifetimes:
6
Extension Methods for Registration
Let us use the service’s Singleton Instance in this example. Modify the Main
method of the Program class as shown below. Which approach do you want to use
to register the service? It is your preference.
namespace FirstCoreMVCWebApplication
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add Framework MVC services to the Container.
builder.Services.AddMvc();
//Application Service
builder.Services.AddSingleton<IStudentRepository, StudentRepository>();
//builder.Services.AddSingleton(typeof(IStudentRepository),
typeof(StudentRepository));
//builder.Services.AddTransient<IStudentRepository, StudentRepository>();
//builder.Services.AddTransient(typeof(IStudentRepository),
typeof(StudentRepository));
//builder.Services.AddScoped<IStudentRepository, StudentRepository>();
//builder.Services.AddScoped(typeof(IStudentRepository),
typeof(StudentRepository));
//Add Application Service to the Container.
//builder.Services.Add(new ServiceDescriptor(typeof(IStudentRepository),
// new StudentRepository())); // by default singleton
7
//builder.Services.Add(new ServiceDescriptor(typeof(IStudentRepository),
// typeof(StudentRepository), ServiceLifetime.Singleton)); // singleton
//builder.Services.Add(new ServiceDescriptor(typeof(IStudentRepository),
// typeof(StudentRepository), ServiceLifetime.Transient)); // Transient
//builder.Services.Add(new ServiceDescriptor(typeof(IStudentRepository),
// typeof(StudentRepository), ServiceLifetime.Scoped)); // Scoped
var app = builder.Build();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
app.Run();
}
}
}
Constructor Injection in ASP.NET Core MVC Application
using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace FirstCoreMVCWebApplication.Controllers
{
public class HomeController : Controller
{
//Create a reference variable of IStudentRepository
private readonly IStudentRepository? _repository = null;
//Initialize the variable through constructor
public HomeController(IStudentRepository repository)
{
_repository = repository;
}
public JsonResult Index()
{
List<Student>? allStudentDetails = _repository?.GetAllStudent();
return Json(allStudentDetails);
}
public JsonResult GetStudentDetails(int Id)
8
{
Student? studentDetails = _repository?.GetStudentById(Id);
return Json(studentDetails);
}
}
}
Code Explanation:
In the above example, the IoC container will automatically inject an instance of
the StudentRepository into the constructor of HomeController. We don’t need
to do anything else. The IoC container will create and dispose of an instance of
the IStudentRepository based on the registered lifetime. Injecting the dependency
object through a constructor is called a constructor dependency injection.
using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
9
namespace FirstCoreMVCWebApplication.Controllers
{
public class HomeController : Controller
{
public JsonResult Index([FromServices] IStudentRepository repository)
{
List<Student> allStudentDetails = repository.GetAllStudent();
return Json(allStudentDetails);
}
}
}
Run the application, and you will get the expected output, as shown below.
The Built-in IoC container does not support property injection. You will have to use
a third-party IoC container.
We can also manually access the services configured with built-in IoC containers
using the RequestServices property of HttpContext. The GetService method in
ASP.NET Core Dependency Injection (DI) is used to retrieve a service from the DI
container. It is provided by the IServiceProvider interface and allows for dynamic
service resolution at runtime. So, modify the HomeController class as shown
below to access the dependent service manually.
using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
10
namespace FirstCoreMVCWebApplication.Controllers
{
public class HomeController : Controller
{
public JsonResult Index()
{
var services = this.HttpContext.RequestServices;
IStudentRepository? _repository =
(IStudentRepository?)services.GetService(typeof(IStudentRepository));
List<Student>? allStudentDetails = _repository?.GetAllStudent();
return Json(allStudentDetails);
}
public JsonResult GetStudentDetails(int Id)
{
var services = this.HttpContext.RequestServices;
IStudentRepository? _repository =
(IStudentRepository?)services.GetService(typeof(IStudentRepository));
Student? studentDetails = _repository?.GetStudentById(Id);
return Json(studentDetails);
}
}
}
With the above changes in place, run the application, and you should get the
output as expected as in the previous examples. It is recommended to use
constructor injection instead of getting it using RequestServices.
I will discuss the Singleton vs. Scoped vs. Transient Services in ASP.NET
Core Application with Examples.
11
Example to Understand Singleton vs. Scoped vs. Transient Services in
ASP.NET Core:
Let us understand the differences between Singleton vs. Scoped vs. Transient
Services in ASP.NET Core with an example.
namespace FirstCoreMVCWebApplication.Models
{
public class Student
{
public int StudentId { get; set; }
public string? Name { get; set; }
public string? Branch { get; set; }
public string? Section { get; set; }
public string? Gender { get; set; }
}
}
IStudentRepository Interface:
namespace FirstCoreMVCWebApplication.Models
{
public interface IStudentRepository
{
Student GetStudentById(int StudentId);
List<Student> GetAllStudent();
}
}
StudentRepository:
namespace FirstCoreMVCWebApplication.Models
{
public class StudentRepository : IStudentRepository
{
//When a new instance of StudentRepository is created,
//we need to log the Date and time into a text file
//using the constructor
public StudentRepository()
{
//Please Change the Path to your file path
12
string filePath = @"D:\MyProjects\FirstCoreMVCWebApplication\
FirstCoreMVCWebApplication\Log\Log.txt";
string contentToWrite = $"StudentRepository Object Created:
@{DateTime.Now.ToString()}";
using (StreamWriter writer = new StreamWriter(filePath, true))
{
writer.WriteLine(contentToWrite);
}
}
public List<Student> DataSource()
{
return new List<Student>()
{
new Student() { StudentId = 101, Name = "James", Branch = "CSE", Section =
"A", Gender = "Male" },
new Student() { StudentId = 102, Name = "Smith", Branch = "ETC", Section =
"B", Gender = "Male" },
new Student() { StudentId = 103, Name = "David", Branch = "CSE", Section =
"A", Gender = "Male" },
new Student() { StudentId = 104, Name = "Sara", Branch = "CSE", Section = "A",
Gender = "Female" },
new Student() { StudentId = 105, Name = "Pam", Branch = "ETC", Section = "B",
Gender = "Female" }
};
}
public Student GetStudentById(int StudentId)
{
return DataSource().FirstOrDefault(e => e.StudentId == StudentId) ?? new
Student();
}
public List<Student> GetAllStudent()
{
return DataSource();
}
}
}
Note: We log the object creation date and time within the Constructor to a text
file. This will tell us how many times the object of the Student Repository class is
created.
SomeOtherService:
13
The Student Repository can also be consumed from a controller or other services.
So, let us create a service that will consume the Student Repository service. So,
create a class file named SomeOtherService.cs and then copy and paste the
following code:
Advertisements
namespace FirstCoreMVCWebApplication.Models
{
public class SomeOtherService
{
//Create a reference variable of IStudentRepository
private readonly IStudentRepository? _repository = null;
//Initialize the variable through constructor
public SomeOtherService(IStudentRepository repository)
{
_repository = repository;
}
public void SomeMethod()
{
//This Method is also going to use the StudentRepository Service
}
}
}
Home Controller:
Please modify the Home Controller as follows. Here, we inject the services of
Student Repository and SomeOtherService through the constructor, and within
the Index and GetStudentDetails, we invoke the methods of both services.
using FirstCoreMVCWebApplication.Models;
using Microsoft.AspNetCore.Mvc;
namespace FirstCoreMVCWebApplication.Controllers
{
public class HomeController : Controller
{
//Create a reference variable of IStudentRepository
private readonly IStudentRepository? _repository = null;
private readonly SomeOtherService? _someOtherService = null;
//Initialize the variable through constructor
public HomeController(IStudentRepository repository, SomeOtherService
someOtherService)
{
_repository = repository;
_someOtherService = someOtherService;
14
}
public JsonResult Index()
{
List<Student>? allStudentDetails = _repository?.GetAllStudent();
_someOtherService?.SomeMethod();
return Json(allStudentDetails);
}
public JsonResult GetStudentDetails(int Id)
{
Student? studentDetails = _repository?.GetStudentById(Id);
_someOtherService?.SomeMethod();
return Json(studentDetails);
}
}
}
Singleton Service in ASP.NET Core Dependency Injection
Singleton services are created the first time they are requested. This single
instance is then used by all subsequent requests throughout the application’s
lifetime. Singleton services are suitable for services that require a shared and
consistent state across the application. Configuration service that reads data from
an external source once and caches it for future use, or a service that manages a
shared resource like a cache or a pool of resources.
Let’s first understand the singleton service with our example. Add the following
code to the Program.cs class file to register the SomeOtherService and
StudentRepository service as Singletons.
builder.Services.AddSingleton<IStudentRepository, StudentRepository>();
builder.Services.AddSingleton<SomeOtherService>();
Singleton service means only one instance of the service is available throughout
the application lifetime, no matter how many times and how many requests are
sent to the server. In our application, the SomeOtherService also uses the
StudentRepository service.
Run the application, access the Index or GetStudentDetails method, and verify the
log file. If you open the log file, you will see the following: Only one object is
created for the StudentRepository service. This proves that a singleton service
means only one instance of the service is available throughout the application’s
lifetime.
15
When to Use Singleton Services:
This is for services that need to maintain state across the entire application.
For services that are expensive to create and can be reused, such as
configuration settings, caching, logging, and database connections.
Scoped services are created once per request. This means one instance is shared
within a single request but separate across different requests. Scoped services
are useful for services that need to maintain data consistency during an operation
and are tied to a specific request, such as a web request in ASP.NET Core. A
database context in Entity Framework Core, where each web request gets its own
database context instance to track changes and perform database operations
committed at the end of the request.
Let’s first understand the scoped service in our example. To register the
SomeOtherService and StudentRepository services as Scoped, add the following
code to the Program.cs class file.
builder.Services.AddScoped<IStudentRepository, StudentRepository>();
builder.Services.AddScoped<SomeOtherService>();
Scoped service means one instance per HTTP request. In our example, when we
send a request to our application (i.e., either to the Index or GetStudentDetails
method), it will create a scoped instance of both SomeOtherService and
StudentRepository service and use the same StudentRepository service within the
SomeOtherService.
First, clear the log file, run the application, access the Index or GetStudentDetails
method, and verify the log file. If you open the log file, you will see the following:
Two objects are created for the StudentRepository service. This proves that
scoped service means one instance per HTTP request. It will create one instance
when we access the Index method and another instance when we access the
GetStudentDetails method. Internally, when it accesses the SomeOtherService, it
will use the same StudentRepository instance.
16
For services that should maintain state within a single request but should
not be shared across different requests.
Ideal for unit-of-work patterns where a single instance should be used
across multiple operations within a request.
Transient services are created each time they are requested from the DI
container. A new instance is provided to every controller, service, or component
that requires it. Since a new instance is created every time, there are no
concurrency issues, but this can lead to higher memory usage if instances are
large or contain a lot of data. A service that performs a simple calculation or
operation and does not maintain any state between requests.
So, let us understand Transient’s service with our example. So, add the following
code to the Program.cs class file to register the SomeOtherService and
StudentRepository services as Transient.
builder.Services.AddTransient<IStudentRepository, StudentRepository>();
builder.Services.AddTransient<SomeOtherService>();
First, clear the log file, run the application, access the Index or GetStudentDetails
method, and verify the log file. If you open the log file, you will see the following:
Four objects are created for the StudentRepository service. This proves that the
Transient service means a new service instance whenever requested. In our
example, when we access the Index action method, it will create one instance
within the Home Controller and another instance within the SomeOtherService,
and this will happen again when we call the GetStudentDetails action method.
17
Transient: A new instance each time it is requested. Use for lightweight,
stateless services.
Choosing the correct service lifetime is essential for optimizing the application’s
performance and resource usage. Understanding and applying these lifetimes
helps ensure your application is efficient and maintainable.
18