Inversion of control (IoC) and dependency injection (DI) are two related but different patterns. The first tells us that we should not depend on actual, concrete classes, but instead on abstract base classes or interfaces that specify the functionality we're interested in.
Depending on its registrations, the IoC framework will return a concrete class that matches our desired interface or abstract base class. DI, on the other hand, is the process by which, when a concrete class is built, the dependencies it needs are then passed to its constructor (constructor injection, although there are other options). These two patterns go very well together, and throughout the book, I will use the terms IoC or DI container/framework to mean the same thing.
.NET always had support for a limited form of IoC; Windows Forms designers used it at design time to get access to the current designer's services, for example, and Windows Workflow Foundation also used it to get registered extensions at runtime. But in .NET Core, Microsoft centralized it and made it a first-class citizen of the ecosystem. Now, virtually everything is dependent on the IoC and DI framework. It is made available in the Microsoft.Extensions.DependencyInjection NuGet package.
An IoC and DI container allow services (classes) to be registered and accessed by their abstract base class or an interface that they implement. Application code does not need to care about the actual class that implements the contract, and this makes it very easy to switch the actual dependencies in the configuration or at runtime. Other than that, it also injects dependencies into the actual classes that it is building. Say, for example, you have this scenario:
public interface IMyService
{
void MyOperation();
}
public interface IMyOtherService
{
void MyOtherOperation();
}
public class MyService : IMyService
{
private readonly IMyOtherService _other;
public MyService(IMyOtherService other)
{
this._other = other;
}
public void Operation()
{
//do something
}
}
If you register a MyService class with the DI container, then when it builds an actual instance, it will know that it will also need to build an instance of IMyOtherService to pass to the MyService constructor, and this will cascade for every dependency in the actual IMyOtherService implementation.
The Host, when it is building the host, initializes an IServiceCollection instance, which is then passed to the Startup class's ConfigureServices method. This is a conventional method that should be used for our own registrations.
Now, a service registration has three components:
The type under which it will be registered (the unique key of the registration)
Its lifetime
The actual instance factory
A lifetime can be one of the following:
Scoped: A new instance of the service will be created for each web request (or scope), and the same instance will always be returned for the same request (scope) whenever we ask the DI framework for it.
Singleton: The instance to be created will be kept in memory, and it will always be returned.
Transient: A new instance will be created whenever it is requested.
The instance factory can be one of the following:
An actual instance, which is always regarded as a Singleton; of course, this cannot be used with the Transient or Scoped lifetimes
A concrete Type, which will then be instantiated as needed
A Func<IServiceProvider, object> delegate that knows how to create instances of the concrete type after receiving a reference to the DI container
You register services and their implementations through theConfigureServices method'sservicesparameter, which is an IServiceCollection implementation:
//for a scoped registration services.Add(new ServiceDescriptor(typeof(IMyService), typeof(MyService), ServiceLifetime.Scoped);
//for singleton, both work services.Add(new ServiceDescriptor(typeof(IMyService), typeof(MyService), ServiceLifetime.Singleton); services.Add(new ServiceDescriptor(typeof(IMyService), newMyService());
//with a factory that provides the service provider as a parameter, from //which you can retrieve //other services services.Add(new ServiceDescriptor(typeof(IMyService), (serviceProvider) => new MyService(), ServiceLifetime.Transient);
There are several extension methods that allow us to do registrations; all of the following are identical:
The DI container also supports generic types—for example, if you register an open generic type, such asMyGenericService<T>, you can ask for a specific instance, such as MyGenericService<ServiceProviderOptions>:
//register an open generic type services.AddScoped(typeof(MyGenericService<>));
//build the service provider var serviceProvider = services.BuildServiceProvider();
//retrieve a constructed generic type var myGenericService = serviceProvider.GetService <MyGenericService<string>>();
It is possible to traverse an IServiceCollection object to see what's already registered. It is nothing but a collection of ServiceDescriptor instances. If we want, we can access individual registrations and even replace one for another.
It is also possible to remove all registrations for a certain base type or interface:
services.RemoveAll<IMyService>();
The RemoveAll extension method is available on the Microsoft.Extensions.DependencyInjection.Extensions namespace.
One very important thing to bear in mind is that any services that implement IDisposable and are registered for either the Scoped or the Transient lifetimes will be disposed of at the end of the request.
The DI framework has the concept of scopes, to which scoped registrations are bound. We can create new scopes and have our services associated with them. We can use the IServiceScopeFactory interface, which is automatically registered and it allows us to do things like this:
var serviceProvider = services.BuildServiceProvider(); var factory = serviceProvider.GetService<IServiceScopeFactory>();
using (var scope = factory.CreateScope()) { var svc = scope.ServiceProvider.GetService<IMyService>(); }
Any scope-bound service returned from the service provider inside the CreateScope inner scope is destroyed with the scope. Interestingly, if any scope-registered service implements IDisposable, then its Dispose method will be called at the end of the scope.
You need to keepa few things in mind:
The same Type can be registered multiple times, but only for the same lifetime.
You can have several implementations registered for the sameType, and they will be returned in a call toGetServices.
Only the last registered implementation for a givenTypeis returned byGetService.
You cannot register a Singleton service that takes a dependency that is Scoped, as it wouldn't make sense; by definition Scoped changes every time.
You cannot pass a concrete instance to a Scoped or Transient registration.
You can only resolve, from the factory delegate, services that have themselves been registered; the factory delegate, however, will only be called after all services have been registered, so you do not need to worry about the registration order.
The resolution will return null if no service from the given Type is registered; no exception will be thrown.
An exception will be thrown if a registered type has on its constructor a nonresolvable type—that is, a type that is not registered on the DI provider.
Several .NET Core APIs supply extension methods that perform their registrations—for example, AddMvc , AddDbContext or AddSession. By default, ASP.NET Core's bootstrap automatically registers the following services:
After all the registrations are done, eventually, the actual dependency framework will be built from the IServiceCollection instance. Its public interface is none other than the venerableIServiceProvider, which has been around since .NET 1.0. It exposes a single method, GetService, which takes aTypeas its single parameterto resolve.
There are, however, a few useful generic extension methods available in theMicrosoft.Extensions.DependencyInjectionpackage and namespace:
GetService<T>(): Returns an instance of the service type that has already been cast appropriately, if one is registered, or null otherwise
GetRequiredService<T>(): Tries to retrieve a registration for the given service type, and throws an exception if none is found
GetServices<T>(): Returns all of the services whose registration keys match (is identical, implements, or is a subclass) to the given service key
You can register multiple services for the same Type, but only the last that is registered will be retrievable using GetService(). Interestingly, all of them will be returned using GetServices()!
Keep in mind that the latest registration for a Type overrides any previous one, meaning that you will get the latest item when you use a GetService, but all of the registrations are returnable by GetServices.
Although the most common usage will probably be constructor injection, where the DI framework creates a concrete type passing it all of its dependencies in the constructor, it is also possible to request at any given time an instance of the service we want, by using a reference to a IServiceProvider, like the one available in the following context:
var urlFactory = this.HttpContext.RequestServices. GetService<IUrlHelperFactory>();
This is called the service locator pattern and some people consider it an antipattern. I won't go over it here, as I believe this discussion is pointless.
The IServiceProvider instance itself is registered on the DI provider, making it a possible candidate for injection!
If, by any chance, you want to build an instance of a type that takes on its constructor services that should come from the DI provider, you can use the ActivatorUtilities.CreateInstance method:
var instance = ActivatorUtilities.CreateInstance<MyType>(serviceProvider);
Or, if we have a reference to a Type, you can use the following:
Finally, I need to talk about something else. People have been using third-party DI and IoC frameworks for ages. .NET Core, being as flexible as it is, certainly allows us to use our own, which may offer additional features to what the built-in one provides. All we need is for our DI provider of choice to also expose an IServiceProvider implementation; if it does, we just need to return it from the ConfigureServices method:
public IServiceProvider ConfigureServices(IServiceCollection services) {
//AutoFac
var builder = new ContainerBuilder();
//add registrations from services
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
}
AutofacServiceProvider also implements IServiceProvider, and therefore we can return it from ConfigureServices and have it as replacement for the out-of-the-box DI container.
All in all, it's very good to see IoC and DI. This is just the basics; we will talk about DI in pretty much all of the rest of this book.
Validating dependencies
Normally, you inject dependencies to controllers (and other components) through their constructors. The problem is, we may not know that a service that we depend upon is missing its registration until it's too late—we try to access a controller that depends upon it and it crashes.
When running in the Development environment, this is checked for us. We register all controllers as services:
Then, when accessing a controller, the web app—any web app, not one that has a specific dependency—ASP.NET Core will try to validate all of the dependencies that it has registered, and, if it finds one for which a dependency is not found, an exception is thrown. This exception will tell you exactlywhat service is missing. ASP.NET Core alsochecks the validity of scoped services—for example, you cannot have a service registered asScoped be retrieved from outside of a scope (usually a web request).
You can actually control this behavior for environments other than Development by adding the following to the bootstrap code in Program:
Note the ValidateOnBuild and ValidateScopesproperties.ValidateOnBuildis for doing what we just saw—testing that the dependency graph is valid—andValidateScopesis for testing that services that require a scope are retrieved from inside one. By default, both are false, except in theDevelopmentenvironment.
So next, let's move on to understand the environments in which we work.