Building a Web API 2 project from scratch using OWIN/Katana .NET Part 5: adding an IoC
July 16, 2015 4 Comments
Introduction
In the previous post we transformed the CustomersApi application a little bit. We added a customer repository and a customer service. We also transformed the Get action method of CustomersController into an asynchronous one.
We ended up having to construct the dependencies of CustomersController in the constructor like this:
public CustomersController() { _customerService = new CustomerService(new CustomerRepository()); }
In this post we’ll get rid of this direct control over the controller dependencies. Instead, we’ll let an ICustomerService object be injected into the controller through its constructor.
Dependency injection
What’s wrong with the above code anyway? The short answer is that CustomersController will directly depend on concrete implementations of certain abstractions. The controller requires an ICustomerService so it builds one in its constructor. However, the concrete implementation of ICustomerService, i.e. CustomerService also requires a dependency of type ICustomerRepository. Therefore the controller must construct it too. So suddenly the constructor is made responsible for fetching concrete implementations for a dependency chain. CustomerRepository could in turn also have some dependency and then CustomersController would have to provide it too, e.g.:
public CustomersController() { _customerService = new CustomerService(new CustomerRepository(new EntityFrameworkUnitOfWork())); }
A long answer as to why this direct responsibility is not ideal requires a lot more words. There are 2 series devoted to the SOLID principles on this blog where the letter ‘D’, i.e. the dependency inversion principle is the most relevant to our discussion:
- SOLID principles in .NET revisited part 7: the Dependency Inversion Principle
- SOLID design principles in .NET: the Dependency Inversion Principle and the Dependency Injection pattern
Our goal is to transform this…
public class CustomersController : ApiController { private readonly ICustomerService _customerService; public CustomersController() { _customerService = new CustomerService(new CustomerRepository()); } ... }
…into its constructor injection equivalent, i.e.:
public CustomersController(ICustomerService customerService) { if (customerService == null) throw new ArgumentNullException("ICustomerService"); _customerService = customerService; }
In fact you can already now rewrite the CustomersController constructor in the CustomersApi like above. This follows the same pattern as we saw in the case of CustomerService:
private readonly ICustomerRepository _customerRepository; public CustomerService(ICustomerRepository customerRepository) { if (customerRepository == null) throw new ArgumentNullException("Customer repository!"); _customerRepository = customerRepository; }
We let the consumer of CustomerService provide an implementation of ICustomerRepository. We want to do the same for CustomersController. The consumer of the controller should provide an implementation of ICustomerService.
However, where do we provide that implementation? Where is the caller to CustomersController? It probably lies hidden within .NET and its MVC routing logic.
IoC containers
Fortunately for us this is not a new problem and has been solved using Inversion of Control (IoC) containers. IoC containers are typically quite complex mechanisms that can “magically” construct concrete implementation of abstract dependencies on the fly. There are many IoC containers out there for .NET and one of the most popular ones is StructureMap.
StructureMap preparations and limitations
StructureMap comes in many flavours. If you open the NuGet package manager and search for “structuremap” you’ll get a lot of different results. We’ll need the one specifically for Web API 2:
It will add several libraries and new files to the project:
These are all auto-generated files. In fact the installer also added MVC-related classes, such as StructuremapMvc.cs. We won’t need all of those.
Also, as we started with an empty application originally you’ll see a couple of build errors. The StructureMap package we’ve installed expects a couple of standard assemblies to be available in the list of references.
We have an additional problem. We installed Helios in this post through NuGet. Helios and StructureMap don’t work well together. I’ve tried to have both Helios and StructureMap in the demo API but haven’t found a good solution. Maybe there’s some kind of a solution out there. We can probably expect a StructureMap update later on when Helios goes live.
Therefore uninstall both pre-release packages that Helios installed:
Also, StructureMap won’t work with OWIN self-host so make sure you set IIS as the deployment platform. I’m not an expert of IoC containers so I’m not sure how to make StructureMap work with the OWIN self-host and Helios. If you know more then you’re welcome to provide your solution in the comments section below.
Next you can safely delete both classes in the App_Start folder. Delete the StructureMapScopeModule class from the DependencyResolution folder as well.
StructureMap-related code
We can now set up StructureMap to find our dependencies. The updated Configuration method in Startup.cs looks as follows:
public void Configuration(IAppBuilder appBuilder) { IContainer container = IoC.Initialize(); container.AssertConfigurationIsValid(); Debug.WriteLine(container.WhatDoIHave()); HttpConfiguration httpConfiguration = new HttpConfiguration(); httpConfiguration.DependencyResolver = new StructureMapWebApiDependencyResolver(container); WebApiConfig.Register(httpConfiguration); appBuilder.UseWebApi(httpConfiguration); }
We initialise the IoC container. We also want to be certain that all the abstract dependencies have been resolved to a certain concrete implementation.
The WhatDoIHave() method will return the resolved types. If you look in the debug window when running the application you’ll find the following:
ICustomerRepository CustomersApi.Model Transient CustomersApi.Repository.CustomerRepository (‘CustomersApi.Repository.CustomerRepository, CustomersApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’) CustomersApi.Repository.Cus… (Default)
————————————————————————————————————————————————————————————————————————————————————————–
ICustomerService CustomersApi.Service Transient CustomersApi.Service.CustomerService (‘CustomersApi.Service.CustomerService, CustomersApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’) CustomersApi.Service.Custom… (Default)
So for ICustomerRepository StructureMap found an implementation called CustomerRepository. Similarly, ICustomerService was resolved to CustomerService.
How did StructureMap know all that? Open DefaultRegistry.cs in the DependencyResolution folder. You’ll find the following call:
scan.WithDefaultConventions();
The default convention for interface abstractions is that if StructureMap sees an interface whose name starts with an “I” then it will look for an implementation with the same name without the “I”: ICustomerService – CustomerServicer, ICustomerRepository – CustomerRepository.
It’s here that you can declare your concrete types for the abstract dependencies, e.g.:
public DefaultRegistry() { Scan( scan => { scan.TheCallingAssembly(); scan.WithDefaultConventions(); }); For<ICustomerRepository>().Use<BrilliantCustomerRepository>(); }
Note the usage of For and Use in the DefaultRegistry constructor.
If you run the application now you’ll see that both ICustomerRepository and ICustomerService are resolved correctly by StructureMap and the customers list is retrieved from the repository.
This is the last post of this short intro to Web API 2 with OWIN. We’ll take up some security extension options in Web API 2 in the next series.
View the list of MVC and Web API related posts here.
Great and very insightful posts, thank you!
Good read, is there a link to download the source ?
Thanks for your comment. Here you are: https://github.com/andras-nemes/webapifromscratchwithowin
//Andras
nice post, i think you have a typo.
in “DefaultRegistry” on the last line:
“For().Use()”
should be:
“For().Use()”
Where is the “Brilliant” came from? 🙂