Using Dependency Injection7:55 with James Churchill
Let's see how we can update our controller to use constructor injection to declare its dependencies. And let's install a DI container to handle creating object instances and managing the lifetime of those instances.
To follow along committing your changes to this course, you'll need to fork the aspnet-fitness-frog-spa repo. Then you can clone, commit, and push your changes to your fork like this:
git clone <your-fork> cd aspnet-fitness-frog-spa git checkout tags/v3.1 -b using-dependency-injection
DI Container Configuration Overview
After installing the
SimpleInjector.Integration.WebApi.WebHost.QuickStart NuGet package, you'll have a new file in your project's App_Start folder: SimpleInjectorWebApiInitializer.cs. Let's walkthrough the code in that file.
The attribute at the top of the file is using a package called WebActivator to wire up the SimpleInjectorWebApiInitializer
Initialize method to be called right after the
Application_Start method has completed. By using this method, Simple Injector avoided having to modify our project's
Application_Start method in the Global.asax.cs file. For more information about WebActivator, see github.com/davidebbo/WebActivator.
Initialize method instantiates and initializes our DI container. The first line of code instantiates the Simple Injector container.
var container = new Container();
The second line of code sets the default scoped lifestyle for our container.
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
Unfortunately, this code uses a deprecated class for the default scoped lifestyle. While this doesn't generate a compilation error, it'll generate a warning. To eliminate the warning, you can optionally update this line of code to:
container.Options.DefaultScopedLifestyle = new SimpleInjector.Lifestyles.AsyncScopedLifestyle();
There are a number of scoped lifestyle types available. It's important to use the one that best suites your application type. For Web API, you want to use the AsyncScopedLifestyle type. That's what this line of code is doing—it's setting the default scoped lifestyle for our DI container to the AsyncScopedLifestyle type.
InitializeContainer method is called, passing in the DI container.
InitializeContainer method is defined just below the
Initialize method. The
InitializeContainer method is where we'll configure the dependencies for our application.
Then a method on the DI container—
RegisterWebApiControllers—is called, which registers all of our application's API controllers as dependencies.
Verify method is called to verify the configuration of the DI container.
If any configuration errors are found, and exception will be thrown. Then Web API's dependency resolver is set to an instance of the SimpleInjectorWebApiDependencyResolver class.
GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
The dependency resolver is responsible for locating dependencies in the DI container. For example, when the DI container is instantiating an instance of the EntriesRepository class, it'll detect that it needs an instance of the Context class to pass to the EntriesRepository's class constructor. The dependency resolver will be used to resolve the Context class dependency to an instance of that class contained within the DI container.
Not only is the dependency resolver used to resolve dependencies between our classes, but it's also used by Web API to resolve controller dependencies. Once Web API has mapped a route to a specific controller, it'll ask the dependency resolver for an instance of that controller class. That's why it was necessary to register our controllers with the DI container.
[MUSIC] 0:00 As we saw on the first section of this course, 0:04 our Visual Studio solution contains a shared class library project 0:07 that contains entity framework related classes. 0:11 These classes, a database context, entities, and repositories 0:15 can be used within the FitnessFrog API to persist data to a database. 0:19 Let's update the entries controller class to make use of these classes. 0:25 Within our entries controller class, we need an instance of the entries repository 0:30 class, which in turn needs an instance of the contents class. 0:35 We could say that the entries controller class 0:39 has a dependency on the entries repository class. 0:42 And the entries repository class has a dependency on the context class. 0:46 Let's start with adding a private field for the entries repository. 0:51 private EntriesRepository, add a using directive, 0:58 the Treehouse.FitnessFrog.Shared.Data namespace. 1:04 Name the private field, entriesRepository, and set its values to null. 1:09 Then add a constructor for the EntriesController. 1:15 Then update the constructor to instantiate an instance of the context class. 1:23 And the EntriesRepository class, passing in an instance of the context. 1:32 This approach is a workable solution, but it does have a couple of drawbacks. 1:45 First, our implementation is incomplete. 1:50 We need to add code to manage the lifetime of the data based context. 1:53 We can do that by overriding the baseclasses virtual dispose method,which 1:58 will give us a hook for calling the dispose method on our context class. 2:03 Second, we're directly instantiating both the context and 2:06 entriesRepository classes, which tightly couples those classes with our controller. 2:11 This will make unit testing our controller more difficult than it needs to be. 2:18 Ideally, what we want to be able to do 2:22 is to add our dependencies as parameters to our constructor, like this. 2:25 EntriesRepository entriesRepository, and 2:32 remove the instantiation of the context class. 2:38 And replace the instantiation of the EntriesRepository class with the reference 2:41 to the parameter. 2:45 By adding an EntriesRepository parameter to our constructor, 2:49 we're declaring that this class has a dependency on the EntriesRepository. 2:52 Where the EntriesRepository instance comes from, and 2:57 how it's instantiated shouldn't be a concern of the EntriesController class. 3:00 Handling dependencies in this manner, 3:04 by using constructor parameters, is known as constructor injection. 3:06 Constructor injection is an implementation of a broader design pattern 3:11 known as dependency injection. 3:15 Overall, dependency injection requires less code, and 3:17 offers an elegant and flexible solution for managing dependencies between classes. 3:21 If you're not familiar with dependency injection and they're be related 3:26 inversion of control design principle, see the teacher's notes for more information. 3:30 So, what instantiates the instance of the EntriesRepository class 3:34 that is being passed to our controller's constructor? 3:38 And what instantiates the instance of the contest class 3:41 that the entry's repository class is dependent upon? 3:45 We need to install and 3:48 configure a dependency injection container, or DI container, for short. 3:50 The DI container is responsible for creating object instances and 3:55 managing the lifetime of those instances. 4:00 There are a number of DI libraries available for .NET. 4:03 For our project, we'll be using the Simple Injector DI library. 4:07 We can use the NuGet package manager to install Simple Injector. 4:11 Simple Injector provides a quick start package 4:15 that will not only add the required assembly reference to our project, but 4:18 will add the code that we need to configure the DI container for web API. 4:22 To install the necessary Simple Injector package, right click on the project, 4:27 and select Manage NuGet Packages. 4:32 Click on the Browse tab if it's not already selected. 4:37 Then search for simpleinjector.integration. 4:41 Here's the SimpleInjector.Integration.WebApi package, 4:50 but we're looking for another package. 4:55 Here it is. 4:59 SimpleInjector.Integration.WebApi.WebHost- ,QuickStart. 5:00 Once you've found the correct package, go ahead and install it. 5:07 Once the package is finished installing, 5:18 open the SimpleInjectorWebApiInitializer.cs file 5:20 located in the App_Start folder. 5:25 The Initialize() method, which is called right after our application is started, 5:32 instantiates and initializes our DI container. 5:37 For a detailed overview of what the code in this method does, 5:40 see the teacher's notes. 5:44 We need to update the initialized container method to register the types in 5:52 our application that we want the DI container to manage as dependencies. 5:56 To do that, we'll use the containers Register method. 6:00 When calling a register method, we specify the type that we want to register 6:11 as the method's generic type parameter. 6:15 Don't forget to add a using statement to the Treehouse.FitnessFrog.Shared.Data 6:21 namespace. 6:26 Then we need to pass in a Lifestyle enumeration value. 6:30 The provided Lifestyle enum value tells the container how many instances of 6:37 a type should be created, and how long each of those instances should leave. 6:42 Simple Injector provides three built in lifestyles. 6:47 Transient, a new instance is created every time that an instance is requested. 6:53 Singleton, only one instance is created per container. 7:00 And Scoped, for every request within an implicitly or 7:06 explicitly defined scope, a single instance will be returned and 7:11 that instance will be disposed when the scope ends. 7:15 Setting the lifestyle to Scoped effectively tells the DI container to 7:21 create a single instance pre-request. 7:25 This means that for any given request, 7:27 there'll only be a single instance of the context type. 7:29 Now, let's register our other two dependencies. 7:33 Entries repository and activities repository. 7:37
You need to sign up for Treehouse in order to download course files.Sign up