ASP.NET Core has dependcy injection available at framework level and ASP.NET Core is heavy user of it. Most of things surrounding controllers, views and other MVC components are implemented as services that web applications consume. This post is intensive overview of dependency injection in ASP.NET Core.
Registering types for dependency injection
Types are registered when application starts. It happens in Startup class. There is method called ConfigureServices() and in the end of this method I usually define service mappings.
public virtual void ConfigureServices(IServiceCollection services)
{
// configure services
var settings = new Settings();
// add MVC and other services
// register dependencies
services.AddSingleton(settings);
services.AddScoped<IFileClient, AzureFileClient>(client =>
{
var connStr = Configuration["StorageConnectionString"];
return new AzureFileClient(connStr);
});
services.AddScoped<IMapperSession, MapperSession>();
services.AddScoped<IProductService, ProductService>();
services.AddTransient<MyClass>();
}
Available scopes for dependencies are:
- Singleton – always return same instance.
- Transient – return new instance every time.
- Scoped – return same instance in current scope (most popular scope is request).
Instance – specific instance is returned every time and it’s up to you how it is actually ceated.
Types registered during application start-up are available to all classes invoked through dependency injection.
Although we can inject types to whatever classes, there are some things made very convenient for us. Let’s see now how injection works with controllers and views. Yes, views too.
Constructor injection
We don’t need custom controller factories anymore if we don’t use some other dependency injection container. Also we don’t have to dig around in system variables and classes to find settings we need. We can do it all through dependency injection.
Of ASP.NET Core UI components constructor injection is supported with controllers, view components and tag helpers.
Here is the example how to provide environment information and some custom services to controller.
public class HomeController : Controller
{
private readonly IApplicationEnvironment _appEnvironment;
private readonly ShopContext _shopContext;
private readonly IProductService _productService;
public HomeController(ShopContext shopContext,
IProductService productService,
IApplicationEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
_shopContext = shopContext;
_productService = productService;
}
public IActionResult Index()
{
return View();
}
// ... more methods follow ...
}
The code here uses constructor injection – the only injecton mode supported by ASP.NET right now. We don’t have to do anything special for dependency injection to happen. We just make sure we register our custom services at application startup.
Username tag helper
Here’s the simple tag helper I have in one web application. Based on claims of authenticated user it writes out name of user.
[HtmlTargetElement("username", TagStructure = TagStructure.NormalOrSelfClosing)]
public class UserNameTagHelper : TagHelper
{
private readonly HttpContext _httpContext;
public UserNameTagHelper(IHttpContextAccessor accessor)
{
_httpContext = accessor.HttpContext;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "";
if (_httpContext.User == null)
{
return;
}
var user = _httpContext.User;
var name = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
var firstName = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
var lastName = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
var id = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
if(!string.IsNullOrEmpty(name))
{
output.Content.Append(name);
return;
}
else if(!string.IsNullOrEmpty(firstName) && !string.IsNullOrEmpty(lastName))
{
output.Content.Append(firstName);
output.Content.Append(" ");
output.Content.Append(lastName);
return;
}
else if(!string.IsNullOrEmpty(firstName))
{
output.Content.Append(firstName);
return;
}
output.Content.Append(id);
}
}
This tag helper uses ASP.NET Core dependency injection to get instance of IHttpContextAccessor.
Injecting all instances of same type
We can inject also multiple instances of same type to controller. For this we must use IEnumerable of given type. Here’s the controller to send hello to current user through all alerting services in web application.
public class HomeController : Controller
{
private readonly IEnumerable<IAlertService> _alertServices;
public HomeController(IEnumerable<IAlertService> alertServices)
{
_alertServices = alertServices;
}
public async Task SendAlerts()
{
foreach(var service in _alertServices)
{
await service.SendAlert(User.Identity.Name, "Hello");
}
}
}
Controller action injection
Constructors of controllers built as kind of business facade may grow big as there can be many instances injected to controller. But every action uses only one or two of these instances. Usually API controllers like these need instances of service classes.
Here’s the example how to inject instance to controller action.
public async Task<IActionResult> UpdateNewsFeeds([FromServices]IUserService userService)
{
if(!await userService.IsAllowedUser(User.Identity.Name))
{
return Forbid();
}
await userService.UpdateFeeds();
return NoContent();
}
Notice FromServices attribute that is needed for ASP.NET Core to understand that userService must be asked from dependency injection.
View injection: Injecting instances to views
It’s also possible now to inject instances to views. There’s new syntax for this.
@model ProductCategoryMenuModel
@inject ShopContext ShopContext
<h1>@ShopContext.PageTitle</h1>
<ul>
<!-- write out categories here -->
</ul>
@inject tells to view engine that we want instance of ShopContext to be injected to view and we name variable holding it as ShopContext.
What about StructureMap, Autofac and others? ASP.NET Core dependency injection can be replaced by other DI/IoC containers that support ASP.NET Core. There are few tricks to know and I have described these in my blog post ASP.NET Core: Using third-party DI/IoC containers.
Conclusion
Framework level dependency injection in ASP.NET 5 is very transparent and configuration is simple. First register type mappings in application start-up and then we use constructor injection to get instances to our classes. This way we can use classes by interfaces and we don’t have to create instances in our own code. On MVC side we can use dependency injection for controller, views and view components.
References
- Using dependency injection with view components
- Injecting services to ASP.NET Core controller actions
- ASP.NET Core: Using view injection
- ASP.NET Core: Inject all instances of interface
- Dependency injection in .NET Core console applications
- Dependency injection in Blazor
- Official documentation (Microsoft)
View Comments (5)
Have you got an example of why it'd be a good idea to inject a service for use directly in a view?
Yes, sure. Consider layout page where you want to show some data on sidebar that is not part of content area filled by controller action. I more see that object injected to view is some custom request context that carries data to layout page and view components so we don't have to use property bags and view bags.
It seems like injecting the service into the view is going against separation of concerns? To keep your view from coupling tightly to the service would it be better to only pass models to the view? Injecting the service into the controller and mapping the return from the service to a model would offer nice clean layers of abstraction.
Casey,
Totally agreed. Just because you *can*, doesn't mean you *should*. In this particular case, I think it was a mistake for the ASP.NET team to implement the feature but perhaps someone will find a legitimate case one day.
Good case is custom request context where one may have properties specific to web site and request. It's possible to inject this additional context to all parts of system where it is needed and it has therefore a little higher flight than ViewBag has. I will write some more posts to cover new stuff in ASP.NET 5 and practical example of view injection is one of these.