Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop Autofac module registering already-registered components

Tags:

c#

autofac

I have an Autofac module which has the following (trimmed down) logic in the Load override:

protected override void Load(ContainerBuilder builder)
{
    foreach (var componentType in allTypesInAllAvailableAssemblies) // Set elsewhere
    {
        var handlerInterfaces = componentType.GetInterfaces().Where(i => i.IsClosedTypeOf(typeof(IMessageHandler<>)));
        if (handlerInterfaces.Any())                    
            builder.RegisterType(componentType).As(handlerInterfaces);
    }
}

This is looking for any class that declares itself a message handler and registers it against all the IMessageHandler interfaces it implements.

What I want to do is not register the component if it's already registered. As a bonus, it would be ideal if I could update the existing registration to resolve against the message handler interface(s) if it isn't already.

For the sake of argument it can be assumed that this code will run after all other types have been registered (including possible message handler candidates)

I've used the AttachToComponentRegistration override for registration manipulation in the past but it doesn't look like it's useful in this scenario.

Is this possible or should I rethink my design and force plugins to explicitly declare their handlers?

like image 724
JRoughan Avatar asked Dec 05 '11 22:12

JRoughan


2 Answers

builder.RegisterType(componentType)
    .As(handlerInterfaces)
    .PreserveExistingDefaults();

Will work.

like image 85
Nicholas Blumhardt Avatar answered Nov 15 '22 19:11

Nicholas Blumhardt


Unfortunately there isn't an elegant way to do what you want. The Autofac container, and its builder, are "black boxes" that don't let you take a good look at what you already have.

There is no harm in registering a component twice, UNLESS your registrations are order-dependent (BAD, BAD, BAD). Registering a second time will simply overwrite the old registration with the new.

I seriously question this code, as it depends totally on how allTypesInAllAvailableAssemblies is initialized. If it really is every type in your system, then it's a crap shoot as to what will resolve as, say, an IDisposable. If you have several different implementations of, say, IConfigurator, you will have limited control over which one ends up registered, regardless of whether you're checking for what's already registered or just letting the registration be overwritten; it depends totally on what class ends up first (or last) in the list.

The only thing I could think to do is use a little Linq to make sure that the list of types you are registering is unique:

protected override void Load(ContainerBuilder builder)
{
    foreach (var componentType in allTypesInAllAvailableAssemblies.OfType<Type>().Distinct()) // Set elsewhere
    {
        var handlerInterfaces = componentType.GetInterfaces().Where(i => i.IsClosedTypeOf(typeof(IMessageHandler<>)));
        if (handlerInterfaces.Any())
            builder.RegisterType(componentType).As(handlerInterfaces);
    }
}

This will guarantee that each instance of componentType has never been seen by the builder before, within the scope of this foreach loop. That means that, given that this is the only module used to build Containers, and each Container is only built once and never updated, each component in the system will have been registered in any given Container exactly once. Common interfaces, like IDisposable, IEnumerable, IComparable, IComparer, etc are going to be worthless to try to resolve; they'll resolve to an instance of the last class that had that interface.

If you have to verify that an interface has never been registered, or that this code also works when using a ContainerBuilder to Update() an existing Container, just stop what you're doing because you are about to create a hopeless mess you will never be able to maintain properly.

like image 40
KeithS Avatar answered Nov 15 '22 17:11

KeithS