It depends Part 1: Contextual Binding

NInject has a very nifty feature called Contextual Binding. It allows users to define in which context to use a specific type mapping. With NInject that is completely baked into the container out-of-the-box. With Unity you need to do some pull-ups to get it.

First you need to capture the neccessary information from Unity’s build pipeline. Which type was originally requested? What is the target for the object that is currently resolved by the pipeline? What dependencies need to be resolved to be able to create the requested object? How to deal with short-circuits in the build pipeline e.g. when an object has a singleton lifetime? What about out-of-band resolves e.g. when you use InjectionFactories where an instance of IUnityContainer is injected?

This is done by a set of custom BuilderStrategies. They are also used to create the hierarchy of requests. If for example you want to create an instance of Foo that has a dependency on an implementation of IBar that needs an ILog which … well, you get it. The hierarchy of those requests must somehow be represented in the structure of the captured information.

What you get

TecX’ implementation mimics that of NInject to a certain degree. Due to the many differences between those two containers it cannot be a 1:1 port though. This is what TecX’ IRequest looks like:

public interface IRequest
{
  IBuilderContext BuilderContext { get; }
  int Depth { get; }
  IRequest ParentRequest { get; }
  IDictionary<string, object> RequestContext { get; }
  Type Service { get; }
  ITarget Target { get; }
  NamedTypeBuildKey BuildKey { get; }
  NamedTypeBuildKey OriginalBuildKey { get; }
  IRequest CreateChild(Type service, IBuilderContext context);
}

It allows access to infrastructure information like the original BuildKey of the request. The BuildKey after Unity performed the type mapping. The Target property tells you into which target the resolved value will be injected (can either be a parameter of a constructor or method or a property). The parent request, if the current request was made to resolve a sub-dependency. How deep down in the resolve hierarchy we are etc.

The implementation class Request provides access to the current request via the static Current property.

More context

You can register additional context information via the (also static) property StaticRequestContext which is a simple key/value store of type IDictionary<string, object>. This property is important for another feature that uses the contextual binding and will be presented in a later post.

As you can see in the code snippet above IRequest also has a RequestContext property. This can be used to store ‘per request’ information that will be shared throughout a single request but is lost at the end of that request. I decided to go for some more convenience and provide a custom implementation of IDictionary<TKey, TValue> that lets you access values from both the static and the per request context in a read through manner. Per request information has precedence over static information. If no per request information with that key is found the lookup is done on the static context next. If you add some values to the dictionary using IRequest.RequestContext this information will only be added to the per request context.

How to use it

There are several extension methods for IUnityContainer. Below you can see the signature of the method taking the most parameters:

public static IUnityContainer RegisterType(this IUnityContainer container,
  Type @from, Type to, LifetimeManager lifetime,
  Predicate predicate, params InjectionMember[] injectionMembers)

It allows you to specify source and target types for the mapping, the lifetime for the created object, a predicate that is validated to find out when to apply the mapping and a set of optional InjectionMembers where you can specify things like constructor to use, interceptors and much more. Overloads of that method add generic registrations, default lifetime etc.

[Teaser] The contextual binding features are also incorporated into TecX’ enhanced fluent configuration API.

This API lets you do the following:

var builder = new ConfigurationBuilder();
builder.For().Use().When(request => { /* ... */ };

or this:

builder.For<IFoo>().Use<Foo>().WhenInjectedInto<Parent>();

[/Teaser]

TecX’ Contextual Binding gives you more fine grained control over your type mappings than simple named mappings do. It also allows you to build far more readable registrations than with nested InjectionConstructors and ResolvedParameters.

Get the source code for Contextual Binding here (project TecX.Unity folder ContextualBinding and the test suite that shows how to use it in TecX.Unity.ContextualBinding.Test).

Advertisements