Unity, Part 10: Custom Build Strategies
Introduction
We’re getting there! This time, custom build strategies, or how you can tell Unity to build/act upon built objects in different ways.
The latest post in the series was on integration with MEF, the previous on web, before that on interfaces, even before I talked about conventions, values, extensions, Aspect-Oriented Programming, dependency injection and the first was an introduction.
Unity allows specifying custom build strategies. Depending on the stage to which they are applied, they can either build objects for us or do something upon newly built ones.
Performing Operations On Newly Built Objects
One example of the latter: suppose we wanted to implement support for ISupportInitialize (I repeat myself, I know). This is a marker interface that features two methods: BeginInit for signaling the start of the initialization process and EndInit for its end. We need a custom build strategy that knows when to call each of these methods. We add build strategies through extensions:
1: public sealed class SupportInitializeContainerExtension : UnityContainerExtension
2: {
3: protected override void Initialize()
4: {
5: var strategy = new SupportInitializeBuilderStrategy();
6: this.Context.Strategies.Add(strategy, UnityBuildStage.Creation);
7: }
8: }
Notice how we add our build strategy to the Creation build stage. This tells Unity that the strategy’s lifecycle methods should be called before and after the object is created. It’s important to remember that only components registered with RegisterType will be intercepted, because those registered with RegisterInstance are, of course, already built.
Extensions are added to the Unity instance:
1: unity.AddNewExtension<SupportInitializeContainerExtension>();
The build strategy itself inherits from BuilderStrategy, which in turn implements IBuilderStrategy; it’s implementation is straightforward:
1: public sealed class SupportInitializeBuilderStrategy : BuilderStrategy
2: {
3: public override void PreBuildUp(IBuilderContext context)
4: {
5: var init = context.Existing as ISupportInitialize;
6:
7: if (init != null)
8: {
9: init.BeginInit();
10: }
11:
12: base.PreBuildUp(context);
13: }
14:
15: public override void PostBuildUp(IBuilderContext context)
16: {
17: var init = context.Existing as ISupportInitialize;
18:
19: if (init != null)
20: {
21: init.EndInit();
22: }
23:
24: base.PostBuildUp(context);
25: }
26: }
PreBuildUp and PostBuildUp are called in sequence just after the object is built (the Existing property). Other lifetime methods exist, which are called at different times, depending on which stage the builder was added to.
Overriding Object Creation
Another example would be intercepting object creation. For that we need another extension:
1: public sealed class CustomBuildExtension : UnityContainerExtension
2: {
3: public Func<Type, Type, String, MethodBase, IUnityContainer, Object> Constructor { get; set; }
4:
5: protected override void Initialize()
6: {
7: var strategy = new CustomBuilderStrategy(this);
8: this.Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
9: }
10: }
The Constructor delegate property takes a number of parameters and returns an object instance. These parameters are:
- First Type: the registered type, such as ILogger;
- Second Type: the concrete type mapped, an ILogger implementation such as ConsoleLogger;
- String: the registered name, such as “console”, or null;
- MethodBase: the method (or property getter/setter) where the component resolution was requested, for context;
- IUnityContainer: the current Unity instance;
We would register it as:
1: var extension = new CustomBuildExtension();
2: extension.Constructor = (from, to, name, method, u) =>
3: {
4: //return something
5: };
6:
7: unity.AddExtension(extension);
The strategy in this case would be like this:
1: public sealed class CustomBuilderStrategy : BuilderStrategy
2: {
3: private readonly CustomBuildExtension extension;
4:
5: public CustomBuilderStrategy(CustomBuildExtension extension)
6: {
7: this.extension = extension;
8: }
9:
10: private IUnityContainer GetUnityFromBuildContext(IBuilderContext context)
11: {
12: var lifetime = context.Policies.Get<ILifetimePolicy>(NamedTypeBuildKey.Make<IUnityContainer>());
13: return lifetime.GetValue() as IUnityContainer;
14: }
15:
16: public override void PreBuildUp(IBuilderContext context)
17: {
18: var stackTrace = new StackTrace();
19: var frame = stackTrace.GetFrame(6);
20: var method = frame.GetMethod();
21: var fromType = context.OriginalBuildKey.Type;
22: var name = context.OriginalBuildKey.Name;
23: var toType = context.BuildKey.Type;
24: var unity = this.GetUnityFromBuildContext(context);
25:
26: context.Existing = this.extension.Constructor(fromType, toType, name, method, unity);
27: context.BuildComplete = true;
28:
29: var lifetimeManager = new ContainerControlledLifetimeManager();
30: lifetimeManager.SetValue(context.Existing);
31:
32: context.Lifetime.Add(lifetimeManager);
33:
34: base.PreBuildUp(context);
35: }
36: }
Worth mentioning:
- A StackTrace instance is used to walk back the stack until our custom method was called;
- The from type, name and to type are obtained from the OriginalBuildKey and BuildKey;
- The Unity instance is a bit more tricky to get, it comes from the current Policies;
- Existing and BuildComplete are set so that the build process is terminated with this builder;
- The Constructor delegate is invoked and should return a proper object inheriting (or implementing) from from type;
- A ContainerControlledLifetimeManager (aka, singleton) is used to track the new object lifetime and added to Unity’s Lifetime collection, so that when Unity is disposed of, the lifetime manager also gets disposed.
And that’s it. This way, you can decide how your object is going to be built and even on which method is it being requested. Hope you find this useful!