Extensibility through Unity

This is the third part (see part I & part II) of my series on Unity & ObjectBuilder. The previous introductory material was only there to make the stuff described below more accessible and the framework described here is in fact a complete addition to the Unity toolset, it allows you:

  • to use Unity much like the Microsoft Extensibility Framework (MEF), i.e. use import/export attributes to combine instances
  • to use Unity to create action flows (workflows in fact but the term has an unintended technical conotation here)
  • to use Unity to inject at runtime other objects and sub-objects through a dotted attribute notation

Before giving you concrete code samples let me first highlight what I wanted to achieve and why I created this extension in the first place.

During the development of GraphSquare (see this article and the architecture series) I needed a flexible mechanism to convert entities and to let the MVC model pass various stages of transformation to serialize things to a variety of export formats (binary, XML, WCF entities and so on). The idea can be best understood as a combination of ORM and serialization. Imagine that you have some domain entities which need to be converted to data entities through a series of actions like depicted in the picture below. Now, the ‘transaction’ here is a package which combines the start and end result and the ‘workflow’ is a series of ‘actions’ which can be chained and which reacts according to what the transaction state (content) is. Obviously, the transformation to an XML output requires a different set of actions than a binary one, yet there is some overlap.

Each action requires either the whole domain entity or some part of it and maybe even some information from the already created data entities. In addition, writing code to assign all this to each instance becomes a repetitive task. So, the idea quickly became involved and difficult to implement. Until I realized that Unity and ObjectBuilder together with their extensibility model were perfectly adapted to this kind of paradigm.

What does it do?

The following snippet shows a typical import/export which injects the exported stuff into a requesting import property.

1
2
3
4
5
6
7
8
9
10
11
12
public class Person
{
    [Import("TheAddress")]
    public Address Address { get; set; }
}
[Export("TheAddress")]
public class Address
{
    public string AddressLine { get; set; }
    public string Zip { get; set; }
    public string City { get; set; }
}

Obviously you could achieve the same with the Dependency attribute from Unity with the difference that here an arbitrary string is allowed. But there is more. The following snippet extends the above with a sub-level injection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person
{
    [Import("TheAddress")]
    public Address Address { get; set; }
    [Import("TheAddress.Zip")]
    public string TheZipCode { get; set; }
}
[Export("TheAddress")]
public class Address
{
    public string AddressLine { get; set; }
    public string Zip { get; set; }
    public string City { get; set; }
}

This mechanism can go as deep as necessary and if a data type or string mismatch occurs the process just stops and the property remains unassigned. Referring to the picture above, this tagging mechanism allows pieces of the transaction to be assigned to actions.

Now, the workflow mechanism require abviously some underlying interface in order to run an action. This can be seen in the snipper below where each action is tagged with the Action attribute which allows action to call each other through a base method implemented as part of that interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[Export("Transaction")]
public class MyTransaction
{
 
}
[Action("Level3")]
public class Level3Action : ActionBase
{
    [Import("Transaction.DataEntity.Name")]
    public string Message { get; set; }
    ///
    /// Runs the action.
    ///
    public override void Run()
    {
        //whatever
    }
}
[Action("Level2")]
public class Level2Action : ActionBase
{
    ///
    /// Runs the action.
    ///
    public override void Run()
    {
        RunAction("Level3");
    }
}
[Action("Level1")]
public class Level1Action : ActionBase
{
    [Import("Transaction.DomainEntity.Name")]
    public string Message { get; set; }
    ///
    /// Runs this workflow.
    ///
    public override void Run()
    {
        RunAction("Level2");
    }
}

The important note here is that everything is handled in the Unity container; the injection of data and running the actions. Calling the workflow is really easy and amounts to the following bits:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void WorkflowTest()
{
    IUnityContainer ctx = new UnityContainer();
    ctx.AddNewExtension< ExtensibilityExtension>();
    ctx.AddNewExtension< WorkflowExtension>();
 
    ctx.RegisterType< Level1Action>();
    ctx.RegisterType< Level2Action>();
    ctx.RegisterType< Level3Action>();
    ctx.RegisterType< MyTransaction>();
 
    bool ret = ctx.RunAction("Level1");
 
}

Of course, the implementation is such that it does not interfere with the registration of named types or instances. That is, you can still use all the standard Unity stuff and register named types even if the export or action name is different.

I have implemented the import/export idea separately from the workflow idea for clarity but merging the code is a blink away.

How does it work?

I’ll first highlight how the import/export stuff is implemented and explain later the ‘action’ or workflow extension.

A Unity extension is created by inheriting from the UnityContainerExtension base class and you can supply in the Initialize method the stuff you want to add to Unity:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ExtensibilityExtension : UnityContainerExtension
{
 
    protected override void Initialize()
    {
        //add the policy bag which will keep track of the registered exports
        Context.Policies.Set< IExportPolicy>(new ExportPolicy(), null);
        Context.Strategies.AddNew< ExtensibilityStrategy>(UnityBuildStage.Initialization);
 
        //hook on the registration events
        Context.Registering += (o, e) => ParseAttributes(e.TypeTo ?? e.TypeFrom, null);
        Context.RegisteringInstance += (o, e) => ParseAttributes(e.Instance.GetType(), e.Instance);
    }

If you want to add a lifetime container, some policies or strategies then this is the place to register them through the ExtensionContext. The ExportPolicy is the policy which will keep the type-name coupling inside Unity. Indeed, the export attribute in essence creates a binding between a Type and a string name. Unity allows you to either register a type or an instance. In the first case we’ll simply store the type-name pair while in the second we’ll also store the instance for future reference.

The Context has two events (Registering and RegisterInstance) which are raised when either a type respectively an instance is registered. Note that these events have nothing to do with strategies or the building process. So, when these events are raised we parse the type (or the instance) for possible ExportAttribute occurence. This parsing is explained in part II of this series and I’ll refrain for repeating myself here. The net result now is that if a type/instance is registered with an Export attribute then the type/name/instance is stored in the IExportPolicy container and whenever the build process needs to check a (export) name the this container is the place to look for. How? Simply by adding a strategy, the ExtensibilityStrategy. This strategy will parse the requested type/instance for Import attributes and look up the IExportPolicy container for the corresponding data, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private void FindImports(IBuilderContext context, object buildKey, object existing)
    {
        if (existing == null)
        {
            return;
        }
        Type t = BuildKey.GetType(buildKey);
 
        foreach (PropertyInfo propertyInfo in t.GetProperties())
        {
            ImportAttribute[] attrs = (ImportAttribute[])propertyInfo.GetCustomAttributes(typeof(ImportAttribute), true);
            if (attrs.Length > 0)
            {
                ImportAttribute impat = attrs[0];
                if (string.IsNullOrEmpty(impat.ImportedName))//name is not assigned
                {
                    throw new ApplicationException(string.Format("Type '{0}' has an Import attribute but without a name.", t.Name));
                }
 
                Debug.WriteLine(string.Format("Type '{0}' needs an exported type with name '{1}'", t.Name, impat.ImportedName));
 
                IExportPolicy apol = context.Policies.Get< IExportPolicy>(null);
                TypedInstance ti = apol.Get(impat.ImportedName);
                if (ti != null)
                {
                    object tobeassigned = ti.Instance;
                    if (tobeassigned == null)
                    {
                        IBuilderContext clone = context.CloneForNewBuild(new NamedTypeBuildKey(ti.Type, ""), null);
                        clone.Strategies.ExecuteBuildUp(clone);
                        tobeassigned = clone.Existing;
                    }
                    object currentObject = tobeassigned;
                    if (impat.ImportedSubName != null)
                    {
                        for (int i = 0; i < impat.ImportedSubName.Length; i++)
                        {
                            try
                            {
                                string currentMemberName = impat.ImportedSubName[i];
 
                                PropertyInfo minfo = currentObject.GetType().GetProperty(currentMemberName);
                                currentObject = minfo.GetGetMethod().Invoke(currentObject, null);
 
                            }
                            catch (Exception)
                            {
                                break;
                            }
 
                        }
 
                    }
                    if (propertyInfo.PropertyType.IsAssignableFrom(currentObject.GetType()))
                        propertyInfo.GetSetMethod().Invoke(existing, new[] { currentObject });
 
                }
                else
                {
                    //The import could not be found!
                    //you can either gracefully notify the user or throw an exception
                    //We will silently ignore the missing import and let the application
                    //logic raise an exception when the property is being used.
 
                }
            }
        }
    }
}

Note in particular the iteration over sub-names which allows you to fetch parts of (registered) objects.

And this is it, except for a few trivial attribute and utility classes declarations. Now, for the workflow/action idea one just repeats the same story but there is a twist. Let me explain.

You will find in the sample code the following extension method for the IUnityContainer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static bool RunAction(this IUnityContainer ctx, string actionName)
{
    try
    {
        IAction action = ctx.Resolve< IAction>(actionName);
        if (action != null)
            action.Run();
        else
            return false;
    }
    catch (Exception)
    {
        return false;
    }
 
    return true;
}

which allows you to run an action on the basis of a name. This works well outside the container because you can call the Resolve method, but how to achieve a chaining of action from inside the container? The easiest way is by means of an event, as can be seen in the definition of the IAction interface:

1
2
3
4
5
6
7
8
9
10
11
12
public interface IAction
{
    ///
    /// Occurs when an  with the name specified is requested (inline chained in the workflow).
    ///
    event EventHandler< ActionArgs> OnAction;
    ///
    /// Runs the action.
    ///
    void Run();
 
}

This event is being parsed by the PostBuildUp method of the strategy like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public override void PostBuildUp(IBuilderContext context)
{
    //let's hook on the action event
    if (context.Existing != null && (context.Existing is IAction))
    {
        IAction action = context.Existing as IAction;
        action.OnAction += (o, e) => RunAction(context, e.ActionName);
    }
}
 
private static void RunAction(IBuilderContext context, string actionName)
{
    //let's resolve the action and iterate the process
    IBuilderContext clone = context.CloneForNewBuild(new NamedTypeBuildKey(typeof(IAction), actionName), null);
    clone.Strategies.ExecuteBuildUp(clone);
    IAction action = clone.Existing as IAction;
    if (action != null)
        action.Run();
}

where we have used the iteration trick explained in the previous article of this series to instantiate a chained action.

While all this might look a little daunting at first, the conceptual path is really straightforward and builts on top of the stuff we explained in part I & part II.

Where is the source code?

I tried to comment as much as possible the code and I hope this series of articles were helpful. You can download the code with a sample console application and I’d hope you find it easier now to build flexible applications on the basis of Unity and develop your own extensions.

Related Posts

G2's interface hierarchy

Some (self-indulgent) overview of G2's interface hierarchy is available.

Read more

The bare bones of Unity interceptions

The Enterprise Library 4.1 was released together with Unity 1.2 in which a whole interception mechanism was added as part of the aspect-oriented approach to coding and a merge of the policy injection ideas. This little note is just a stepping stone if you wish to play with the new interception namespace.

Read more

5 Responses to Extensibility through Unity

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

top