.NET Technical Guide: AutoFac
Introduction
What is AutoFac?
The principle of Dependency Inversion states that you should rely on abstractions instead of concretions. It leads to code that is more scalable, testable, and maintainable because it allows us to quickly and easily swap out implementations (examples of this concept are in the following sections). While this can be manually implemented, dependency management tools like AutoFac will automate the process for you. This technical guide will demonstrate the principle of Dependency Inversion and it’s implementation using AutoFac to manage your dependencies.**** ****
Dependency Inversion
What is it and why does it matter?
What does it mean to rely upon abstractions instead of concretions? To put it simply, it means we should strive to use interfaces in place of the implementations of those interfaces. A more concrete example of what this looks like in a program is explored in a later section. The reference section can also provide more information.**** ****
Taking advantage of dependency inversion can provide several advantages in your code:
- ****Testability:****Injecting implementations of classes at runtime allows us the flexibility to use either the intended production implementation or a mocked version of that class. This swapping makes writing effective and encompassing unit tests a breeze.
- Separation of Concerns: Good programming practice dictates that independent logic should be isolated from unrelated functionality. Dependency inversion compliments this practice by forcing you to separate your unrelated logic into isolated classes.
- Extensibility/Scalability: When you have the ability to swap out implementations very quickly, it allows you extend the functionality of your system. For example, it is very simple to swap out one implementation of a class for another implementation with higher performance.
- ****Maintainability:****Dependency injection allows you to change implementations by changing a few lines instead of hunting down and manually swapping out multiple instances across your system.
One of the most commonly used methods of adhering to dependency inversion is called dependency injection. The key to dependency injection is to “inject” (meaning pass in a version of the class in question at runtime) the implementations of classes at runtime, which allows the code to be flexible in it’s implementations. To illustrate what dependency injection is, let’s explore a simple example.**** ****
Dependency Injection: A Simple Example
An illustration of why to use dependency injection
For our example we will create three main components: a driver class to run the console application, a status monitor class that is responsible for monitoring something, and a status writer for the status monitor to use to output the status. We will go through several iterations of code; first setting up the program, then introducing dependency injection, and finally adding AutoFac and leveraging it’s automated dependency handling.**** ****
FIrst, create a project in Visual Studio. I will be creating a C# Console Application for the purposes of simplicity. I named my main class Driver.cs and also created a helper class called StatusMonitor.cs, as seen in Figures A and B.
**** **** The last class we need to write is the OldStatusWriter. The OldStatusWriter simply prints out a string to the console (indicating its “status”) via the WriteStatusToConsole method. OldStatusWriter uses the built-in object StreamWriter to do the actual work of printing to the console. The actual implementation of OldStreamWriter isn’t super important for this example, but the source code is included for reference (Figure C).**** ****We will also need to implement StatusMonitor to use OldStatusWriter to print out a status. The implementation of this can be seen in Figure D.**** ****
**** **** When we run this console application, it will pop open the console, print out its status, and then wait for a key to be pressed before closing.**** ****Now that we have a foundation, let’s remember the problem we had in the initial example: having to replace an implementation.**** ****
Before we dive into how AutoFac can handle this situation for us, let’s make our lives a little easier by laying the groundwork for using dependency injection. The first thing we’ll do is create an interface for OldStatusWriter: IStatusWriter**** ****
Now, instead of creating a new instance of OldStatusWriter within the StatusMonitor, let’s make it a parameter to the constructor. This will allow us the freedom to use any status writer that we want, as long as it implements the IStatusWriter interface.**** **** Just as a sanity check, let’s re-run our program now that we’ve implemented the beginnings of dependency injection. It should still output the old status. Don’t forget to pass an instance of OldStatusWriter to the StatusMonitor in the Driver!**** **** So now that we have some infrastructure in place, let’s swap out our OldStatusWriter with the NewStatusWriter (Figure H). For the purposes of this example, the only real difference between the Old and New status writers will be the status that it outputs.**** ****Because we have created IStatusWriter and passed an instance of it into the StatusMonitor, all we need to do is have NewStatusWriter implement the IStatusWriter interface. Then we can pass in a NewStatusWriter as a parameter when we create a new StatusMonitor in the Driver class (Figure I).**** ****
**** **** If we run our program again, we see that it prints out a new status, indicating that it is now using the NewStatusWriter. With one simple change, we have now replaced all the instances of OldStatusWriter within project with NewStatusWriter.**** ****We can register our interfaces and classes with AutoFac, and it will inject whatever implementation we designate for every instance of each interface. This registration code can even be pulled out into AutoFac Modules for better separation of interests. The following sections will cover AutoFac set up, registering classes and interfaces, and making Modules.**** ****
AutoFac
Introducing AutoFac to the example**** ****
Now we will introduce AutoFac to our project and set it up to handle all the dependency injection for us. The first thing to do is include the AutoFac package. Open up the package manager, select “Online”, search for AutoFac and install it.**** ****
First, we will register the OldStatusWriter with AutoFac. To register types with AutoFac, We must create a ContainerBuilder (called builder in the example code). For each type we want to register, we call builder.RegisterType().**** ****The ContainerBuilder class is used to register all the Interfaces you wish to be controlled by AutoFac. By calling the ContainerBuilder’s “RegisterType()” method, you can tell AutoFac to register classes as instances of interfaces. When you register a class as an interface, AutoFac will then handle the injection of the actual class through the interface at runtime.**** ****
Once registered, how do you actually get the implementation of an interface to use in your code? There are two main ways of doing this. The first way is to let AutoFac inject the implementation into the constructor of the class using it. This method is the most common approach.**** ****
But what if you’re in a situation where you can’t simply inject an implementation into a constructor? In cases like that, you can use the Container we set up earlier to actually resolve the interface to it’s implementation. See Figure K: when we call “builder.Build()”, we are telling AutoFac that we are done registering and wish to start receiving implementations from the container. This is done by calling “container.Resolve()”. When resolved, the container searches its registered interfaces for one that matches the arguments (See Figure K, line 14) and injects whatever implementation is registered for that interface.**** ****
In the snippet below, we have registered the OldStatusWriter as an instance of the IStatusWriter interface. This tells AutoFac to inject a newly created instance of OldStatusWriter anywhere that the code is using IStatusWriter. When you run this code, it will report the status from the OldStatusWriter, as expected.**** ****
Now, let’s register our newer type, NewStatusWriter. We will register it in the Driver class directly after the OldStatusWriter, to demonstrate AutoFac’s ability to inject on the fly. We will then print out the status again (See Figure L).**** **** When you run this iteration of our project, the console prints out both the old status and the new status. This happens because we first registered OldStatusWriter as IStatusWriter and printed it’s status, and then re-registers NewStatusWriter to IStatusWriter and printed it’s status. AutoFac knows which implementation to inject based on what implementation you register.**** ****AutoFac allows us to inject whatever implementation of an interface that we want into the code at runtime. One powerful way to leverage this functionality is feature switching. Feature switching is a concept where you use different implementations of things in different environments.Thanks to AutoFac, feature switching is as simple as modifying the registration command. Let’s do a simple example.**** ****
Since we now have some duplication of code, let’s pull out the common code into a shared method.**** ****
As always, let’s test this again to make sure we don’t have any problems. Running the application should result in both status messages printing out to the console.**** ****While we’re at it, why don’t we use AutoFac to manage our StatusMonitor as well? This is not necessary, but it is good practice because it enforces the concept of what kinds of classes we want to inject versus instantiate. There are three main types of classes in this sense:
- ****Services:****A class that operates on more than one other class or depends on more than one other class. One way to tell if a class falls into this category is if you are instantiating other classes within it. For an example of what a service might look like, see the FAQ section.
- Models: A model is a class that primarily contains properties. A model can have methods, but those methods only operate on members of that class. For an example of what a model might look like, see the FAQ section.
- Helpers: A helper is a class that contains logic, but only performs that logic on data that is passed into the helper. Helpers are typically static classes. See the FAQ section for more examples of helper classes.**** ****
Services are usually the most numerous and are the only classes that are candidates for injection. Dependency injection is useful for swapping out implementations; it doesn’t make sense to swap out a model or helper who really only have one implementation of their functionality. For services, simply create an interface for any classes that are necessary dependencies and pass those interfaces into the dependent method as constructor arguments.**** ****
We will implement an IStatusMonitor interface and use that to inject the implementation in at runtime.**** ****
**** **** Great! Now we have removed all of the places where we have created a new instance of a class (except for AutoFac classes, of course). This makes our code more maintainable and testable – maintainable because the injection allows us to easily swap out the implementation of components, and testable because we can write unit tests that inject mocked versions of dependencies that we can test against.**** ****Let’s clean up our Main method a little bit by pulling out the StatusMonitor creation into its own method. We will use the same Container object that we used for setting up the StreamWriter.**** ****
Even though this is a very simple and small project, our Main method feels cluttered with all the AutoFac set up happening in it. This is where the concept of a Module shines. A module is an AutoFac construct that is used to handle all the AutoFac set up and keep that set up out of your code’s logic. The power of modules is they can be separated logically with the structure of your project. In this guide, we use one module for the entire project, but it makes sense to split your modules up by purpose. For example, it is common to have one module for every different part of your system. This could mean one module per project, one module per logical segment of code, or anything in between.**** ****Create a new class called StatusWriterModule, and we will use it to set up our status writer implementation. For the purposes of this example, we will write one module that takes in a status writer type, so that we can injection either kind of status writer.**** ****
Notice how the StatusWriterModule implements the Module interface, which is built in to AutoFac. The Module interface requires the implementation of one method called Load. In our case, we use Load to inject one or the other type of stream writer that we have created.**** ****Finally, let’s call the Load() method from the Driver class so we can take full advantage of AutoFac. In Figure R, you can see that we have significantly simplified our Driver code by calling the StatusWriterModule.**** ****
We’ve taken a look at what dependency inversion and dependency injection are, and how they aid in having good code. We’ve also introduced AutoFac and shown how to use it within your code. To get more information, see the Reference section. If you have any questions, see the Contact section. Happy coding!**** ****FAQ**** ****
Q: How do I include AutoFac with my project?
A: You can import the AutoFac package to your Visual Studio solution using the package manager console. See Figure I for more information.
Q: How to register an interface / inject it’s implementation
A: To register an interface and it’s associated implementation, you must first create a ContainerBuilder, and use it to register your interface and associated implementation.The code looks similar to the following:
builder.RegisterType(Interface)
See Figure K or L for more information.
Q: What might a service look like?
A: Spotting a service is usually as simple as spotting instantiations of new classes and seeing those classes used to perform encapsulated logic. An example of what a service might look like is provided below. Notice how several classes are instantiated (PaymentReceiver and PaymentLogger), and seem to be performing complex logic behind the scenes:
public class ProcessPaymentService { /* Constructor is here */ public bool ProcessPayment() { var paymentReceiver = new PaymentReceiver(); var paymentInformation = paymentReceiver.ReceivePayment(); var paymentLogger = new PaymentLogger(); var logSuccessful = paymentLogger.LogPayment(paymentInformation); return logSuccessful; } }
Q: What might a model look like?
A: Keep in mind that a model is primarily made up of properties and is usually used to carry information rather than perform logic. A model can have methods, but those methods only operate on the properties of the model itself. An example of a model for an individual might look like this:
public class Individual { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return string.Format("{0}{1}", FirstName, LastName); } } }
Q: What might a helper look like?
A: Typical helpers are things like string formatters and data type converters. The following code illustrates a date helper that takes in a date string in the MM/DD/YYYY format and converts it into a DateTime object:
public static DateConverterHelper() { public static DateTime ConvertStringToDateTime(string date) { // preform validation to check for MM/DD/YYYY format first return new DateTime(date); } }
Reference
More information on the SOLID programming principles can be found here:
http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)**** ****
More information on Dependency Injection can be found here: http://tutorials.jenkov.com/dependency-injection/dependency-injection-benefits.html**** ****
More information on AutoFac can be found here:
http://autofac.org/**** ****