The different built-in implementations of ITarget provide us with the ability to resolve objects in different ways - and this section looks at those implementations in addition to how we actually perform registrations.
This subject means delving into the ITargetContainer interface and our default implementation, TargetContainer - which contains the core registration API, as well as some extra functionality which might be useful to some.
What is ITargetContainer?
The ITargetContainer interface supplies service registrations when using the default container types Container and ScopedContainer. All the target container does is to provide a means to register and look up ITarget instances which have been registered against specific types.
Registering via extension methods
The majority of your work with the Rezolver framework will use extension methods to create and add targets to an ITargetContainer which will ultimately drive the IContainer that you will use to create instances.
These extension methods are more expressive than the core API (documented below), and greatly simplify your interaction with the Rezolver framework.
The majority of the code samples shown on this site use these registration methods, which are documented in the API section of this site:
- AliasTargetContainerExtensions - for registering aliases of one type to another (useful for reusing singletons for multiple types)
- DecoratorTargetContainerExtensions - for registering decorators
- DelegateTargetContainerExtensions - for registering factory delegates
- ExpressionTargetContainerExtensions - for registering expression trees
- MultipleTargetContainerExtensions - for batch registering multiple targets
- ObjectTargetContainerExtensions - for registering object references/values
- RegisterTypeTargetContainerExtensions - for registering constructor-injected types (plain and open-generic)
- ScopedTargetContainerExtensions - for registering scoped constructor-injected types
- SingletonTargetContainerExtensions - for registering singleton constructor-injected types
Registering via Register
To add registrations to an ITargetContainer directly, i.e. without extension methods, we ultimately use the
Register method, which accepts an ITarget object and an optional
which that target is to be registered.
If the optional type is not provided, then the target's DeclaredType is used as the default registration type:
var targets = new TargetContainer(); targets.RegisterObject("hello world"); var target = targets.Fetch(typeof(string)); Assert.IsType<ObjectTarget>(target);
So, here, the target container defaults to using
System.String as the registration type for the target
because that's its DeclaredType. We can also provide any base or interface of the target's type
as a valid type, too:
var targets = new TargetContainer(); targets.Register(Target.ForType<MyService>(), typeof(IMyService)); var target = targets.Fetch(typeof(IMyService)); Assert.IsType<ConstructorTarget>(target);
MyService implements the interface
IMyService in this example
If you attempt to register a target against a type which the target does not support then an exception will occur:
var targets = new TargetContainer(); // int is obviously not compatible with IMyService. Assert.Throws<ArgumentException>( () => targets.Register(Target.ForObject(50), typeof(IMyService)));
THE DOCUMENTATION AFTER THIS POINT IS PRIMARILY AIMED AT DEVELOPER WHO ARE LOOKING TO EXTEND REZOLVER
As illustrated by earlier examples, you can interrogate the registrations in an ITargetContainer through two methods:
- Fetch - retrieves the last-registered target for the type
- FetchAll - retrieves all targets that have been registered for the type, in the order they were registered.
These same methods are used by the standard container classes when determining how to resolve an instance (or instances) for a given type.
There is currently no way to remove a registration from a ITargetContainer. You can, however, create an OverridingTargetContainer to override the registrations of another, and pass that as the target container to a new Container or ScopedContainer.
The different ways in which Rezolver can create/obtain objects for your application, then, are pretty much all handled through the ITarget interface, and the different implementations that are available.
Whether you want to use constructor injection (via ConstructorTarget or GenericConstructorTarget), an object you've built yourself (via ObjectTarget), an expression tree (via ExpressionTarget) or a factory delegate (via DelegateTarget), or something else, there's lots of ways to get Rezolver to build the services you want to use in your application.
All the targets used by default in Rezolver to create objects can be found in the Rezolver.Targets namespace. In addition to their constructors, the Target static class also contains numerous factory methods for building these targets in isolation, using an API which is similar to the registration API. For example: ForType is a shortcut for building the ConstructorTarget, which binds a type for constructor injection.
In the table of contents to the left (or above if on a small screen) you'll also find high-level walkthroughs, including examples, on the most important targets you need to know about.
You can also implement ITarget yourself if you're feeling adventurous - but you must provide a way for the container to compile your target into an ICompiledTarget that can be used at resolve-time. Documentation on how to do this will be added to this guide in the future, but if you're curious now, then the types in the Rezolver.Compilation.Expressions namespace will provide a few ideas.
Rezolver containers also support short-circuited, 'direct' targets which bypass the compilation process when attempting to fulfil a Resolve operation, specifically:
- If the target also supports the ICompiledTarget interface, then its GetObject method will be used to get the object.
- If the target can be cast to the type originally requested through the Resolve method, then target will be returned as the object.
The framework exploits both of these techniques to use the container as the source of its own services and configuration.