Creating and using a container
Using Rezolver is the same as with all other IOC containers:
- Create and configure the root container
- Register the services which your application needs
- Resolve services from the container
All of this setup is ultimately conducted through just a few primary types, which we'll take a brief look at now.
For all the built-in container types, Rezolver splits registration and resolving responsibilities between two interfaces:
You'll notice that the constructors for the Container and ScopedContainer types (see next)
actually accept an instance of IRootTargetContainer. This is a new interface added in v1.3.2 which
marks a target container as a 'top-level' one - and supports functionality such as
IRootTargetContainer also implies the
This interface describes a registry of ITarget objects, keyed by type, describing the type of object that is to be created (and how) when a given type is requested.
It is through this interface that you setup your container with registrations, which are then later used when resolving objects.
The primary implementation of this interface that you will use in your application is TargetContainer - either created implicitly by the framework or explicitly in your own code.
This is the interface through which we resolve objects. The interface does not expose any registration mechanisms at all (even if the classes providing the 'standard' implementations all do) - only the ability to request objects from the container.
This interface does not mandate that a container has an
IRootTargetContainer, it's simply the case
that all the provided implementations which we will discuss in the rest of this documentation do
use that interface as the source of their service registrations.
Creating a container
There are numerous ways to create an IContainer instance. The easiest, from the point of view of registering services and then resolving them, is to create an instance of the Container type. Or, if you want your root container to act as a global lifetime scope for explicitly scoped objects then you can also use ScopedContainer:
// create a standard non-scoping container var container = new Container(); // or create a scoped container: var container = new ScopedContainer();
All code samples assume you have added a
using statement (
imports in VB) for the
Once you have a local reference to either of these classes, you can start registering services via the container's ITargetContainer implementation, and resolving objects from it.
As mentioned above, with our default implementations of IContainer, registration of services ultimately means adding targets to the container's Targets target container, associating them with service types which we will later resolve.
Here's an example where we register the type
Foo to be created whenever an instance of
IFoo is requested,
directly via a container's own implementation of ITargetContainer:
var container = new Container(); container.RegisterType<Foo, IFoo>();
var targets = new TargetContainer(); targets.RegisterType<Foo, IFoo>(); var container = new Container(targets);
There's too many types of registrations to cover in a few sub headings here for this - the service registration topic has more detail, with links to all the other different types of registration you can perform.
Moreover, you can browse the different topics from the table of contents on the left (or top on mobile).
Resolving objects from your container is done through the Resolve method which, you'll
notice, accepts an IResolveContext as its single parameter, and returns an
Object. Again, if
you're familiar with IOC containers then you're probably wondering where your strongly-typed
Well, fear not. As with the many extension methods available on ITargetContainer, the IContainer interface (through which all resolving of objects is done) has extension methods to provide a more traditional IOC API, and these are found on the ContainerResolveExtensions static class.
The one you'll use most frequently, of course, is the Resolve<TObject>(IContainer)
method, which provides the aforementioned
So, assuming we have the
container that we've been using up till now, we would resolve an instance of
simply by doing one of the following:
MyService service = container.Resolve<MyService>(); //or MyServiceBase service = container.Resolve<MyServiceBase>(); //or IMyService service = container.Resolve<IMyService>();
The IResolveContext interface captures the context of a call to the
Resolve method, tracking the
container on which the call is originally made, whether there is an IContainerScope active for the call,
and other things besides. You will rarely use it directly in application code unless you are extending Rezolver.
Resolve extension methods create the
IResolveContext for a given operation on your behalf, so you never
have to worry about it.
Assuming the container can locate the service registration for the type you request, it will fetch/produce an instance of the associated object type according to the behaviour described by the ITarget which we previously registered.
If no registration is found, then an
InvalidOperationException is raised by the container.
IContainer also implements the
System.IServiceProvider interface from the .Net framework, which requires
that missing services yield a
null result instead of throwing an exception.
Sometimes you might want to attempt to resolve an object from the container, but not have an exception raised
if it cannot be found. In this case you can use the TryResolve method, which returns the
object via an
out parameter and returns a
bool indicating whether the operation succeeds.
Naturally, as with the
Resolve operation, this method has a generic overload
(TryResolve<TObject>(IContainer, out TObject)) to avoid the need for a
MyService result; bool success = container.TryResolve(out result); //success == true or false depending on registrations.
Similarly we can also introspect a container to find out if it can resolve an instance of a given type by using the CanResolve method. This method also has friendly overloads via extension methods (e.g. CanResolve<TObject>(IContainer))):
bool canResolve = container.CanResolve<MyService>();
Container Configurations and Options
For those looking to customise or extend Rezolver, many of the types are overridable. However, the ITargetContainer and IContainer implementations mentioned above also use another mechanism that provides extensibility without having to subclass them.
This is an advanced topic and not one that you should have to worry about most of the time. The examples in this guide will highlight where you can use the functionality described below - this section is intended to be a high-level overview only.
There are two primary types of container configuration in Rezolver:
- Target container configuration (via implementations of ITargetContainerConfig)
- Container configuration (via implementations of IContainerConfig)
Both are very similar in that they define a method called
which is passed an ITargetContainer and, in the case of IContainerConfig, also an
Implementations of the interfaces can add/modify service registrations which are then used either directly by the container, or which provide more advanced registration functionality.
For example, Rezolver's automatic enumerable injection is enabled by the InjectEnumerables configuration when it configures an ITargetContainer. This configuration is actually applied to all instances of TargetContainer by default (via the DefaultConfig configuration collection) - but you can also control whether enumerable injection is enabled without having to remove the configuration from that collection, as is shown in the last enumerable example.
There is much more to be covered about configuration and options. For now - use them where this guide shows you can (e.g. to control contravariance or member binding behaviour etc), and if you want to be able to control something else this way, and can't, then just open an issue on Github.