Dependency injection best practices for ASP.NET Core MVC 5

Learn how to use constructor injection, property injection, method injection, and the service locator pattern in ASP.NET Core MVC 5, along with their caveats.

Dependency injection best practices for ASP.NET Core MVC 5
Thinkstock

Dependency injection is a first-class citizen in ASP.NET Core MVC 5. Part of the new .NET 5, ASP.NET Core MVC 5 is the latest version of Microsoft’s framework for building web apps and APIs using the model-view-controller design pattern.

In this article we’ll take a deep dive into dependency injection in ASP.NET Core MVC 5 as well as learn the best practices that one should follow when working with dependency injection.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.

Create an ASP.NET MVC 5 project in Visual Studio 2019

First off, let’s create an ASP.NET Core project in Visual Studio 2019. Following these steps will create a new ASP.NET Core MVC 5 project in Visual Studio 2019.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web App (Model-View-Controller)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  7. Click Next.
  8. In the “Additional Information” window shown next, select .NET 5.0 as the target framework from the drop-down list at the top. Leave the “Authentication Type” as “None” (default).
  9. Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Razor runtime compilation” are unchecked as we won’t be using any of those features here.
  10. Click Create.

A new ASP.NET Core MVC 5 project will be created. We’ll use this project in the subsequent sections of this article.

Dependency lifetimes in ASP.NET Core MVC 5

When working with ASP.NET Core MVC 5 you must register a service to be able to use it. When you register a service with the container, you can specify the service lifetime. There are three possible lifetimes:

  1. Transient: The components of the service will not be shared but will be created each time they are requested.
  2. Scoped: One instance of the components per scope (i.e., one instance per request to the application) will be created. Components are reused within the scope.
  3. Singleton: The components of the service will be created once for the lifetime of the application and will be shared by all requests.

You should write your code to register dependencies using any of the above three approaches in the ConfigureServices method of the Startup class. You can learn more about making services available via dependency injection from my earlier article here.

Dependency injection example in ASP.NET Core MVC 5

Let’s now create the required classes and interfaces to implement dependency injection.

The Author class given below is an entity class.

public class Author
 {
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
 }

The AuthorService class extends the IAuthorService interface and implements its methods.

 public class AuthorService : IAuthorService
 {
    public bool CreateAuthor(Author author)
    {
       //Write your own implementation here
       return true;
    }
    public List<Author> GetAllAuthors()
    {
       List<Author> authors = new List<Author>();
       //Write your own implementation here
       return authors;
    }
  }
public interface IAuthorService
 {
   bool CreateAuthor(Author author);
   List<Author> GetAllAuthors();
 }

Types of dependency injection

The built-in dependency injection provider in ASP.NET Core is not as feature-rich as containers like StructureMap and Ninject, but it is quite fast and easy to configure and use.

We can use the following types of dependency injection in ASP.NET Core MVC 5:

  • Constructor injection: The injector passes the dependency via the constructor of the client class.
  • Property injection: The injector passes the dependency via the public property of the client class.
  • Method injection: The injector passes the dependency via a method parameter of a public method pertaining to the client class

Implement constructor injection in ASP.NET Core MVC 5

The following code snippet illustrates how Constructor injection can be implemented.

public class HomeController : Controller
 {
        private readonly IAuthorService _authorService;
        public HomeController(IAuthorService authorService)
        {
            _authorService = authorService;
        }
     //Other methods
 }

Constructor injection best practices

When using constructor injection, you should mention all required dependencies in the parameters of the constructor. You should also assign the dependency to a read-only object.

Implement property injection in ASP.NET Core MVC 5

Although ASP.NET Core MVC 5 doesn’t include support for property injection using its built-in dependency injection container, it’s easy to overcome this limitation. The following code snippet shows how you can implement property injection in ASP.NET Core MVC 5.

public class AuthorService : IAuthorService
    {
        public ILogger<AuthorService> Logger { get; set; }
        public bool CreateAuthor(Author author)
        {
            Logger = NullLogger<AuthorService>.Instance;
            //Write your own implementation here
            return true;
        }
        public List<Author> GetAllAuthors()
        {
            List<Author> authors = new List<Author>();
            //Write your own implementation here
            return authors;
        }
    }

Property injection best practices

You must ensure that you use the null object pattern or validate that the dependency is null before you pass it using the property injection technique. To avoid runtime errors, you should only allow non-null objects. Furthermore, you should take advantage of property injection only for optional dependencies, i.e., for dependencies without which the service will still work.

Implement method injection in ASP.NET Core MVC 5

Consider the following three interfaces:

 public interface IBusinessLogic
    {
        void SetDbManager(ISQLServerManager iSQLServerManager);
    }
 public interface ISQLServerManager
    {
        //Write your own implementation here
    }
 public interface IOracleManager
    {
        //Write your own implementation here
    }

The IBusinessLogic interface represents the type that contains the declaration of members related to the business logic, which is given below. The other two interfaces represent manager classes for SQL Server and Oracle Database. Ideally, these interfaces should include the declarations of database CRUD operations.

public class AuthorBusinessLogic : IBusinessLogic
    {
        ISQLServerManager _iSQLServerManager;
        public void SetDbManager(ISQLServerManager iSQLServerManager)
        {
            _iSQLServerManager = iSQLServerManager;
        }
    }

The AuthorBusinessLogic class implements the IBusinessLogic class and contains one method, named SetDbManager. The SetDbManager method receives an instance of any of the two classes (not shown in this example for brevity) that implement the two interfaces shown earlier. It then assigns this object to the _iSQLServerManager instance. And that's it! You can now write your own code to pass dependencies using method injection.

Method injection best practices

You must ensure that the dependency is not null before you pass it using the method injection technique.

Implement the service locator pattern in ASP.NET Core MVC 5

The service locator design pattern is yet another way in which you can retrieve dependencies. The service locator pattern allows code to be added dynamically at run time — even without recompiling or restarting the application. So, applications can selectively add or remove items from the service locator dynamically (it uses a central registry). The example code snippet below illustrates how this pattern can be used.

public class BookService
    {
        private readonly IBookRepository _bookRepository;
        public BookService(IServiceProvider serviceProvider)
        {
            _bookRepository = serviceProvider
              .GetRequiredService<IBookRepository>();
        }
        //Other methods
    }

Service locator pattern best practices

The service locator pattern can be used when we would like to avoid dependencies between implementations of an interface and the client code. This pattern can also be used when we would like to locate services that are otherwise costly to create or manage.

However, we should avoid using the service locator pattern in most cases because it makes the dependencies implicit rather than explicit. That is, we are not able to view the dependencies when creating the instance of the service.

Dependency injection is a technique that facilitates loose coupling and improves code maintenance and testability. You can learn more about the core concepts related to dependency injection in my earlier article on the topic.

How to do more in ASP.NET Core:

Copyright © 2021 IDG Communications, Inc.