Updated : Use Dependency Injection in ASP.NET Core 1.0
Dependency Injection (DI) is a technique to develop loosely coupled software
systems. Although this article won't go into the details of DI as such, the
basic idea is this:
Assume that there is a class Class1 that depends on Class2 and Class2 for its
functioning. The normal practice is to instantiate the objects of Class2 and
Class3 inside Class1 as shown below:
public class Class1
{
private Class2 objClass2;
private Class3 objClass3;
public Class1()
{
objClass2 = new Class2();
objClass3 = new Class3();
}
....
....
}
Although this might work as expected it has a problem of its own - there is
tight coupling between Class1 and its dependencies (Class2 and Class3). If you wish to replace
Class2 and Class3 with some other implementations you need to change the code of Class1
since these objects are created inside the class.
To get away from this dependency you can do the following:
- Base Class2 and Class3 on some interface.
- Supply objects of classes implementing the interface (Class2 and Class3
in this case) from external world.
In other words you inject dependencies of a class from the external world.
So, Class1 will look like this:
public class Class1
{
public Class1(ISomeInterface objClass2,
ISomeOtherInterface objClass3)
{
....
}
}
Ok. Now that you have some idea about DI, let's see how ASP.NET Core 1.0 allows
you to work with it.
As far as ASP.NET Core 1.0 is concerned, a type to be injected is called a
service. The ASP.NET Core dependency injection framework does two basic
tasks for you:
- It instantiates an object of the said service and supplies it to
controllers. The dependencies can be injected through constructor injection
or through property injection.
- It handles the lifetime (when to create an object and when to dispose
it) of the injected object.
There are three lifetime modes for a service being injected:
- Singleton : An object of a service is created and
supplied to all the requests to that service. So, basically all requests get
the same object to work with.
- Scoped : An object of a service is created for each and
every request. So, each request gets its a new instance of a service to work
with.
- Transient : An object of a service is created every
time an object is requested.
Let's see how each of these modes work.
Begin by creating a new ASP.NET Core 1.0 project as outlined
here. Then add an
interface and a class to the Classes (or some other folder of you choice)
folder:
public interface IMyServiceInterface
{
string UniqueID { get; set; }
}
public class MyService:IMyServiceInterface
{
public string UniqueID { get; set; }
public MyService()
{
this.UniqueID = Guid.NewGuid().ToString();
}
}
The MyServiceInterface class defines a single property - UniqueID. The
MyService class implements IMyServiceInterface. The UniqueID property is set to
a new GUID in the constructor of the MyService class. This way you can observe
the value of UniqueID to understand the lifetime modes mentioned above.
Then add HomeController and Index view to the project.
Singleton
The DI services and their lifetime modes are registered in the Startup class.
So, open the Startup class and modify the ConfigureServices() method as shown
below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IMyServiceInterface,MyService>();
}
Notice the line marked in bold letters. It uses the AddSingleton() method of
the IServiceCollection to register MyService class as a service type. As the
name suggests AddSingleton() method registers MyService with singleton mode.
Next, open the HomeController and write the following code to it:
public class HomeController : Controller
{
private IMyServiceInterface obj;
public HomeController(IMyServiceInterface obj)
{
this.obj = obj;
}
public IActionResult Index()
{
ViewBag.Obj = this.obj;
return View();
}
}
The HomeController constructor takes a parameter of IMyServiceInterface.
Since a type that implements this interface is registered with the DI framework,
an object of MyService is created and supplied to the constructor. The object
reference is stored in a local obj variable for later use.
The Index() action, simply sets a ViewBag property to this injected object.
The Index view outputs the value of UniqueID property as follows:
<h4>UniqueID of Obj : @ViewBag.Obj.UniqueID</h4>
If you run the application your browser should look something like this:
Now open another browser tab and issue another request to /home/index. You
will find that both the tabs output the same UniqueID confirming that a
singleton object is being created.
Scoped
To change the lifetime mode to Scoped, open Startup class again and change
the call as shown below:
services.AddScoped<IMyServiceInterface,MyService>();
This time the code uses AddScoped() method. After making this change run the
application again. Simulate multiple requests as before. This time you will find
that each tab displays different UniqueID. This conforms that each request is
being supplied with a new object instance.
Transient
To test the transient mode you need to change the Startup class like this:
services.AddTransient<IMyServiceInterface,MyService>();
This time the code uses AddTransient() method to register MyService. Now, to
simulate multiple object creation requests within a single request-response
cycle modify HomeController as shown below:
public class HomeController : Controller
{
private IMyServiceInterface obj1;
private IMyServiceInterface obj2;
public HomeController(IMyServiceInterface obj1,
IMyServiceInterface obj2)
{
this.obj1 = obj1;
this.obj2 = obj2;
}
public IActionResult Index()
{
ViewBag.Obj1 = this.obj1;
ViewBag.Obj2 = this.obj2;
return View();
}
}
This time the HomeController declares two variables of IMyServiceInterface.
The constructor accepts two parameters of IMyServiceInterface - obj1 and obj2.
This is just to simulate multiple object creation requests. The obj1 and obj2
are stored in ViewBag as before.
The Index view outputs the UniqueID from both the objects like this:
<h4>UniqueID of Obj : @ViewBag.Obj1.UniqueID</h4>
<h4>UniqueID of Obj2 : @ViewBag.Obj2.UniqueID</h4>
To test the transient mode, run the application. You will find that the two
UniqueID values are different even for a single HTTP request.
Special case of Singleton - instance
You can register a singleton service by creating its instance yourself. In this case you are responsible for
creating an object of a service. The DI framework then uses that instance in
singleton mode mentioned earlier. For example, you can create an instance of MyService and register it with
the DI framework. You can do so in ConfigureServices() as shown below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
MyService obj = new MyService();
services.AddSingleton<IMyServiceInterface>(obj);
}
As you can see, the code creates an object of MyService instead of relying on
DI framework. This manually created object instance is then
registered with the DI framework using an overload of AddSingleton() method.
Notice that this overload accepts an object instance that is then registered as
a singleton instance with the system.
You can run the application as before. You will observe that instance mode works like
singleton lifetime mode - the same object instance is supplied to all the
requests.