stimms
stimms

Reputation: 44046

Resolving a collection of services from a service type

I have a rather complex bit of resolving going on in Autofac. Basically I want all the objects in the container which implement a specifically named method with a specific argument type. I have implemented some somewhat insane code to get it for me

var services = (from registrations in _componentContext.ComponentRegistry.Registrations
                            from service in registrations.Services
                            select service).Distinct();
foreach (var service in services.OfType<Autofac.Core.TypedService>())
{
    foreach (var method in service.ServiceType.GetMethods().Where(m => m.Name == "Handle"
                                                                    && m.GetParameters().Where(p => p.ParameterType.IsAssignableFrom(implementedInterface)).Count() > 0))
    {
        var handler = _componentContext.Resolve(service.ServiceType);
        method.Invoke(handler, new Object[] { convertedMessage });
    }
}

My problem arises in that the handler returned the the resolution step is always the same handler and I cannot see a way to resolve a collection of the the registrations which are tied to the service as one might normally do with container.Resolve>().

I feel like I'm pushing pretty hard against what AutoFac was designed to do and might do better with a MEF based solution. Is there an easy AutoFac based solution to this issue or should I hop over to a more composition based approach?

Upvotes: 0

Views: 939

Answers (1)

Nicholas Blumhardt
Nicholas Blumhardt

Reputation: 31757

G'day,

In MEF you could use 'Method Exports' for this (http://mef.codeplex.com/wikipage?title=Declaring%20Exports) but that might be a bit drastic. There are a couple of ways to achieve what you want in Autofac.

You can make the above code work by searching for registrations rather than services:

var implementorMethods = _componentContext.ComponentRegistry.Registrations
    .Select(r => new {
        Registration = r,
        HandlerMethod = r.Services.OfType<TypedService>()
            .SelectMany(ts => ts.ServiceType.GetMethods()
                .Where(m => m.Name == "Handle" && ...))
            .FirstOrDefault()
    })
    .Where(im => im.HandlerMethod != null);

foreach (var im in implementorMethods)
{
    var handler = _componentContext.ResolveComponent(im.Registration, new List<Parameter>());
    im.HandlerMethod.Invoke(handler, new object[] { convertedMessage });
}

I.e. implementorMethods is a list of the components implementing a handler method, along with the method itself. ResolveComponent() doesn't rely on a service to identify the implementation, so there's no problem with the service not uniquely identifying a particular implementor.

This technique in general will probably perform poorly (if perf is a concern here) but also as you suspect will work against the design goals of Autofac (and MEF,) eliminating some of the benefits.

Ideally you need to define a contract for handlers that will let you look up all handlers for a message type in a single operation.

The typical recipe looks like:

interface IHandler<TMessage>
{
    void Handle(TMessage message);
}

Handlers then implement the appropriate interface:

class FooHandler : IHandler<Foo> { ... }

...and get registered at build-time like so:

var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(FooHandler).Assembly)
    .AsClosedTypesOf(typeof(IHandler<>));

To invoke the handlers, define a message dispatcher contract:

interface IMessageDispatcher
{
    void Dispatch(object message);
}

...and then its implementation:

class AutofacMessageDispatcher : IMessageDispatcher
{
    static readonly MethodInfo GenericDispatchMethod =
        typeof(AutofacMessageDispatcher).GetMethod(
            "GenericDispatch", BindingFlags.NonPublic | BindingFlags.Instance);

    IComponentContext _cc;

    public AutofacMessageDispatcher(IComponentContext cc)
    {
         _cc = cc;
    }

    public void Dispatch(object message)
    {
        var dispatchMethod = GenericDispatchMethod
            .MakeGenericMethod(message.GetType());

        dispatchMethod.Invoke(this, new[] { message });
    }

    void GenericDispatch<TMessage>(TMessage message)
    {
        var handlers = _cc.Resolve<IEnumerable<IHandler<TMessage>>>();
        foreach (var handler in handlers)
            handler.Handle(message);
    }
}

...which is registered like so:

builder.RegisterType<AutofacMessageDispatcher>()
    .As<IMessageDispatcher>();

The component that feeds in the messages will then resolve/use IMessageDispatcher to get the messages to the handlers.

var dispatcher = _cc.Resolve<IMessageDispatcher>();
dispatcher.Dispatch(message);

There are still ways to do this without the interface, but all rely on creating some kind of contract that uniquely defines handlers for a particular message (e.g. a delegate.)

In the long run the generic handler pattern will be the easiest to maintain.

Hope this helps, Nick.

Upvotes: 1

Related Questions