Choosing an IOC Container – Pt 2

DryIoc is an IOC container for .NET that is built for speed. Looking at the link we mentioned previously, you will notice DryIoc performs better than all the other containers that were examined. It is very focused, well maintained, and has complementary packages to tie it into ASP.NET Core, Web API, OWIN, etc. That said, my favorite aspect of DryIoc is its extensibility which makes it easy to use!

Implementation

When implementing any IOC container, a certain level of coding is required. In my case there are a couple pieces I would like to make sure are covered.

  • Automatically register types via convention
  • Use transient lifecycle but allow overrides
  • Allow the registration of types outside of our convention

Now if you remember from my previous post, yes there are other containers that support these features out of the box. Unfortunately, this comes at the cost of performance. This is why I chose DryIoc. It doesn’t have all the extra fluff the other containers do but, it performs very well.

My basic approach is as follows.

  1. Get a collection of conventional mappings
  2. Get non-conventional life-cycles
  3. Get non-conventional mappings
  4. Iterate through mappings and register with container
Conventional Registration

Using a convention is nice because you don’t have to wire up every single class and interface. The first step in doing this is scanning for all our types.

foreach (var file in dllFiles)
{
    var name = Path.GetFileNameWithoutExtension(file);
    Assembly assembly;

    try
    {
        assembly = AppDomain.CurrentDomain.Load(name);
    }
    catch (Exception)
    {
        try
        {
            assembly = Assembly.LoadFrom(file);
        }
        catch (Exception ex)
        {
            throw new ActivationException($"Could not load assembly '{file}'", ex);
        }
    }

    if (assembly != null)
    {
        yield return assembly;
    }
}

Once we have the types loaded, we can check to see if any of them follow our convention. I will usually pass in a Func to use as the convention. This decouples things a bit and allows our consuming application to override the convention if needed.

private Func<Type, Type> GetMappingConvention()
{
    return t => t
    .GetInterfaces()
    .SingleOrDefault(
    i => i.Name.Equals($”I{ t.Name}”, StringComparison.OrdinalIgnoreCase));
}
Custom Life-Cycles

To handle life cycle, I like to create some attributes to put on the implementing class.

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class SingletonAttribute : Attribute
{
}

This way, all we need to do is decorate our classes with one of the lifecycle attributes… and voila! We can keep track of these and take it in considerations when we register the types with the container.

[Singleton]
public class SomeCache
{
...
}

In the past I’ve also thought about using a naming convention for the lifecycle. For example, if we came across a type suffixed with the word Cache we could automatically register it as a singleton. Remember since we are doing all the wireups, we have all the power 🙂

Custom Mappings

Not everything will follow our convention so, we will still want to find a way to override the conventional mappings. There are a couple of options.

  • Hardcode
  • XML Configuration
  • etc

I won’t go into detail here. The point is, we have options. Essentially we want to gather the additional mappings and add them in with the rest.

Registration

Once we have all of our mappings, we can register them with DryIoc.

var interfaceType = convention.Invoke(implementationType);
if (interfaceType != null)
{
    container.Register(
    serviceType: interfaceType,
    implementationType: implementationType,
    made: Made.Of(t => t.GetConstructor(Type.EmptyTypes)),
    reuse: reuse,
    ifAlreadyRegistered: IfAlreadyRegistered.Keep);
}

Summary

Now we are able to harness the speed of DryIoc and have the benefits of the convenience features that some of the other containers offer. Furthermore, we are in no way tied to DryIoc. The way we’ve implemented our code makes it very easy to swap out containers if we’d like to down the road.

Sounds like a pretty good deal 😉