Dependency injection (DI) has been possible in previous versions of MVC. With each new version DI has been easier to implement and, with MVC6, DI is supplied right out of the box. In this article we’ll look at how the new DI implementation works, what are its weaknesses and how we can replace it with our favorite DI framework.
What’s new
The unification of APIs across ASP.NET is a common theme throughout ASP.NET 5, and dependency injection is no different. The new ASP.NET stack including: MVC, SignalR and Web API, etc. rely on a built-in minimalistic DI container. The core features of the DI container have been abstracted out to the IServiceProvider interface and are available throughout the stack. Because the IServiceProvider is the same across all components of the ASP.NET framework a single dependency can be resolved from any part of the application.
The DI container supports just 4 modes of operation:
- Instance – a specific instance is given all the time. You are responsible for its initial creation.
- Transient – a new instance is created every time.
- Singleton – a single instance is created and it acts like a singleton.
- Scoped – a single instance is created inside the current scope. It is equivalent to Singleton in the current scope.
BASIC SETUP
Let’s walk through setting up DI in a MVC application. To demonstrate the basics, we’ll resolve the dependency for the service used to get project data. We don’t need to know anything about the service other than that it implements the IProjectService interface, an interface custom to our demo project. IProjectService has one method,GetOrganization(), that method retrieves an organization and its corresponding list of projects.
public interface IProjectService
{
string Name { get; }
Organization GetOrganization();
}
public class Organization
{
public string Name { get; set; }
[JsonProperty("Avatar_Url")]
public string AvatarUrl { get; set; }
public IQueryable<Project> Projects { get; set; }
}
We’ll use the IProjectService to get the organization data and display it in a view. Let’s start by setting up the controller where the service will be used. We’ll use constructor injection by creating a new constructor method for our controller that accepts anIProjectService. Next, the Index action will callGetOrganization, sending the data to the view to be rendered.
private readonly IProjectService projectService;
public HomeController(IProjectService projectService)
{
this.projectService = projectService;
}
public IActionResult Index()
{
Organization org = projectService.GetOrganization();
return View(org);
}
If we try to run the application at this point we’ll receive an exception because we haven’t yet added a concrete implementation of ourIProjectService to the DI container.
InvalidOperationException: Unable to resolve service for type 'DependencyInjectionMVC6Demo. Services. IProjectService' while attempting to activate 'DependencyInjectionMVC6Demo. Controllers. HomeController'.
Microsoft. Framework. DependencyInjection. ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
The exception message shows that the code fails during a call toActivatorUtilities.GetService. This is valuable information because it shows that in MVC6 the DI container is already involved in the controller’s construction. Now we just need to tell the container how to resolve the dependency.
In order to resolve the dependency, we need a concrete implementation ofIProjectService. We’ll add a DemoService class and, for simplicity, it will use static dummy data.
public class DemoService : IProjectService
{
public string Name { get; } = "Demo";
public Organization GetOrganization() => new Organization
{
Name = this.Name,
AvatarUrl = $"http://placehold.it/100&text={this.Name}",
Projects = GetProjects()
};
private IQueryable<Project> GetProjects() => new List<Project> {
new Project {
Id = 0,
Description = "Test project 0",
Name = "Test 0",
Stars = 120
},
//...
new Project {
Id = 4,
Description = "Test project 4",
Name = "Test 4",
Stars = 89
}
}.AsQueryable();
}
Finally, we’ll instruct the DI container to instantiate a new DemoServicewhenever IProjectService is required. To configure the container we’ll modify the ConfigureServices method in Startup.cs. Our configuration will be added to the end of this method.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//... other services
// Add MVC services to the services container.
services.AddMvc();
//our services
}
The service is added by using the AddTransient extension method on the services collection, and setting the IProjectService as the type of service and the DemoService as the implementation.
public void ConfigureServices(IServiceCollection services)
{
//... other services
// Add MVC services to the services container.
services.AddMvc();
//our services
services.AddTransient<IProjectService, DemoService>();
}
With the service added, DemoService will now be instantiated when the controller is created, and the exception will no longer be thrown.