Dependency Injection to The Core (Part - I)

Okay since I’m writing blogs mostly on asp.net core lately, you might be wondering that why am I writing a blog on a topic which is already available in asp.net core official documentation.

Well, in this post I’m not only going to talk about how you can achieve DI (Dependency Injection) in .net core but will also discuss about DI itself (what, why and how). This article reflects how I actually learned (still learning) DI. So, if you don’t want to follow my approach or don’t want a deep dive into DI then I’ll suggest you follow the official link from asp.net core documentation provided below.

https://docs.asp.net/en/latest/fundamentals/dependency-injection.html

If you are reading this paragraph, then maybe you are interested in a deep dive into the wonderful world of DI with me. So, are you ready? I guess you are. Let’s get started!

An application is made up of different components coupled together. Coupling is good but what about strict coupling? That’s definitely bad. According to the second principle of the very well-known software design principles (simply called SOLID),

"Software entities (components/class/module/functions) should be open for extension, but closed for modifications (ca be achieved with abstract classes/interfaces)."

Since we need to make our application extensible over time, we need to make sure different components in our application is loosely coupled together. DI is a way in which we can achieve loose coupling in our application.

In enterprise level, we often work with n-tier applications. For our example let’s assume that we have this 3-tier application.

It is basically a Web API application. So, all I did is I exposed some data over the client (presentation layer) using APIs. Here, we have AngularJs on the client-side (presentation layer). We have our API controllers in the business layer and we have our entity framework database context to do the dirty works in the data access layer.

In this case, we really don’t have to worry about DI in the presentation layer because we can do DI in client side separately. Frameworks like AngularJS provides their own way of implementing DI on Client-side. Notice that I said frameworks (AngularJs, EmberJs, BackboneJs) not libraries (JQuery, Knockout). You can use libraries and make your own framework where people can implement DI in your provided way.

I’m making things clear so that you can’t poke me later saying, “you didn’t do DI in the presentation layer”. DI is really just a concept and a technique to learn, upon learning, you can use the same concept and technique in any kind of application framework of your like. This can be either server side framework or client-side framework. So, don’t mix things up.

Today I’ll show you how you can achieve dependency in ASP.NET Core. So, that’s server-side. But before that let’s see what is the current state of our application. Let’s take a look into the business layer and data access layer.

A snapshot from the data access layer would be, (don’t try to understand the code.)


public class TodoRepository
{
    private readonly TodoContext _context = new TodoContext();

    public IEnumerable<Todo> GetAll()
    {
        return _context.Todos;
    }

    public void Add(Todo item) 
    { 
        _context.Todos.Add(item);
        _context.SaveChanges();
    }

    public Todo Find(int id)
    {
        Todo todo = _context.Todos.AsNoTracking().FirstOrDefault(t => t.Id == id);
        return todo;
    }

    public Todo Remove(int id)
    {
        Todo todo = _context.Todos.FirstOrDefault(t => t.Id == id);
        _context.Todos.Remove(todo);
        _context.SaveChanges();

        return todo;
    }

    public void Update(int id, Todo item)
    {
        Todo todo = _context.Todos.FirstOrDefault(t => t.Id == id);
        todo.Title = item.Title;
        todo.IsDone = item.IsDone;
        _context.SaveChanges();
    }
}

Notice that, here we are creating a new instance of TodoContext every time we are calling the TodoRepository class. Our TodoRepository is working as a wrapper which eventually talks to a specific DbSet defined in the TodoContext. Here is how our TodoContext looks like,


class TodoContext : DbContext
{
    public TodoContext() : base("TodoDbConnectionString")
    {

    }
    public DbSet<Todo> Todos { get; set; }
}

N.B: TodoDbConnectionString is the connection string name.

We have the API controller in the business layer (middle layer). If you are still trying to understand the code, then don’t. We are here to learn DI not how to make a n-tier application.


public class TodoController : ApiController
{
    private readonly TodoRepository _todoRepository = new TodoRepository();
    // GET: api/Todo
    public IEnumerable<Todo> Get()
    {
        return _todoRepository.GetAll();
    }

    // GET: api/Todo/5
    public Todo Get(int id)
    {
        return _todoRepository.Find(id);
    }

    // POST: api/Todo
    public void Post([FromBody]Todo todo)
    {
         _todoRepository.Add(todo);
    }

    // PUT: api/Todo/5
    public void Put(int id, [FromBody]Todo todo)
    {
        _todoRepository.Update(id, todo);
    }

    // DELETE: api/Todo/5
    public void Delete(int id)
    {
        _todoRepository.Remove(id);
    }
}

Notice that, here again we are creating a new instance of TodoRepository every time we are calling the Api controller. Always remember creating a new instance means gluing stuff together. And that’s exactly what we are doing in the TodoController and TodoRepository class. What if we change our mind and want to use another repository (for example, a repository that can do CRUD operations against a file system) in the controller instead of the current one? Again, what If we want to use a different data access library in the repository instead of the current one. Here’s what we will do when no one is watching,

We will go to those individual classes and then replace the instantiations with the new types. But doing this will break the second rule of the SOLID design principle. (Here, not only we are making our application inextensible but we are also modifying it each time our requirement is changing).

To be continued...