Introduction to ASP.NET Core part 19: dependency injection into views
March 1, 2017 Leave a comment
Introduction
In the previous post we saw some other examples of creating custom views in .NET Core MVC. We first discussed how to set the HTML content before and after some text using the “spandec” custom attribute. It enclosed a piece of text within a span element and added a class attribute to the span element as well. We then saw how easy it was to provide the custom tag with an object from the HTML markup using an attribute. Therefore we’re not confined to supplying primitive types to custom tags as arguments. Lastly we saw how to create a conditional tag helper to filter out specific Book records based on an if-condition.
In this post we’ll briefly look at dependency injection into views.
Dependency injection in views
Before in this series we looked at handling dependency injection in Controllers and other parts of the system in Startup.cs. Here’s a short example on how we did it:
public void ConfigureServices(IServiceCollection services) { //rest of demo code ignored services.AddSingleton<IStringFormatter, JsonStringFormatter>(); }
In our IndexController we use an injected IBookService to get all the books from an IBookRepository which in turn was also injected into the implementation of IBookService. It is really smooth to wire up dependencies this way.
However, what if a view file also needs some dependency injected into it? Normally the controller should provide the view with all the details it needs in order to build a piece of HTML. The view should not go out and fetch data from a service or a repository. In this new version of MVC it’s possible to declare and inject dependencies in views. However, this is a good case of “just because you can, it doesn’t mean you should”. So you should have a very good reason to using the following technique.
We use the @inject directive in a view to declare the object type to be injected. Recall our previous IStringFormatter interface:
namespace DotNetCoreBookstore.Dependencies { public interface IStringFormatter { string FormatMe(object input); } }
…and its implementation, the JsonStringFormatter class:
using DotNetCoreBookstore.ProjectDependencies; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System; namespace DotNetCoreBookstore.Dependencies { public class JsonStringFormatter : IStringFormatter { private readonly IGreeter _greeter; private readonly IConfiguration _configuration; private readonly ProductMeasures _productMeasures; private readonly FavOptions _favOptions; public JsonStringFormatter(IGreeter greeter, IConfiguration configuration, ProductMeasures productMeasures , IOptions<FavOptions> favourites) { if (greeter == null) throw new ArgumentNullException("Greeter"); if (configuration == null) throw new ArgumentNullException("Configuration"); if (productMeasures == null) throw new ArgumentNullException("Measures"); if (favourites == null || favourites.Value == null) throw new ArgumentNullException("Favourites"); _greeter = greeter; _configuration = configuration; _productMeasures = productMeasures; _favOptions = favourites.Value; } public string FormatMe(object input) { return JsonConvert.SerializeObject(new { Greeting = _greeter.SendGreeting(), Content = input, Mood = _configuration["Mood"], Measures = _productMeasures, Favourites = _favOptions }); } } }
We’ve wired up all the dependencies in the ConfigureServices function of Startup.cs for the JsonStringFormatter class to be injected anywhere it’s needed. Here’s an example of injecting an IStringFormatter into the _Layout.cshtml view:
@using DotNetCoreBookstore.Dependencies @inject IStringFormatter StringFormatter <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> <div> @RenderSection("FunnyMessage", false) </div> <div> <p>@StringFormatter.FormatMe(new { Message = "Hello from the Layout view using dependency injection", Warning = "Use this technique sparingly"})</p> </div> </body> </html>
The inject directive requires a type and a name. The type can be an abstraction or a concrete type. If it’s an abstraction then its concrete type should be declared in the ConfigureServices function:
services.AddSingleton<IStringFormatter, JsonStringFormatter>();
…otherwise code execution will fail with the following message:
An error occurred while starting the application.
InvalidOperationException: No service for type ‘DotNetCoreBookstore.Dependencies.IStringFormatter’ has been registered.
The name property declares the name of the variable which invokes the dependency in the C# code of the view. This can be any string like “ElvisPresley” or “BarbraStreisand”. See how “StringFormatter” is invoked in the final paragraph.
If you test the above code then you’ll see that JsonStringFormatter was correctly invoked and all views using the layout view will have an extra paragraph:
{"Greeting":"Good morning","Content":{"Message":"Hello from the Layout view using dependency injection","Warning":"Use this technique sparingly"},"Mood":"HiHi","Measures":{"Width":123,"Height":867,"Length":456,"Type":"3D"},"Favourites":{"Music":"Rock","Food":"TomatoSoup","Technology":".NET"}}
We’ll look at partial views in the next post.
View the list of MVC and Web API related posts here.