2011-01-10 6 views
0

Ich habe ein ziemlich komplexes Auflösungsbit in Autofac. Grundsätzlich möchte ich alle Objekte im Container, die eine spezifisch benannte Methode mit einem bestimmten Argumenttyp implementieren. Ich habe einige etwas verrückt Code implementiert, um es für mich zu bekommenAuflösen einer Sammlung von Diensten von einem Diensttyp

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 }); 
    } 
} 

Mein Problem darin, dass die Prozedur entsteht zurück die der Auflösungsschritt immer der gleiche Handler ist und ich kann nicht sehen, wie man eine Sammlung den die Anmeldungen zu lösen die mit dem Service verbunden sind, wie es normalerweise mit container.Resolve>() möglich ist.

Ich habe das Gefühl, dass ich ziemlich hart dagegen anklage, wofür AutoFac entwickelt wurde und vielleicht besser mit einer MEF-basierten Lösung. Gibt es eine einfache AutoFac-basierte Lösung für dieses Problem oder sollte ich zu einem kompositionsbasierten Ansatz übergehen?

Antwort

1

G'day,

In MEF könnten Sie 'Methode Export' für diesen Einsatz (http://mef.codeplex.com/wikipage?title=Declaring%20Exports), aber das ist vielleicht ein bisschen drastisch sein. Es gibt eine Reihe von Möglichkeiten, um in Autofac zu erreichen, was Sie wollen.

Sie können den obigen Code Arbeit machen, indem für die Registrierung der Suche anstatt Dienstleistungen:

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 }); 
} 

D.h. implementorMethods ist eine Liste der Komponenten, die eine Handler-Methode zusammen mit der Methode selbst implementieren. ResolveComponent() verlässt sich nicht auf einen Dienst, um die Implementierung zu identifizieren, daher gibt es kein Problem damit, dass der Dienst ein bestimmtes Implementierer nicht eindeutig identifiziert.

Diese Technik wird im Allgemeinen wahrscheinlich schlecht funktionieren (wenn Perf ist hier ein Problem), aber auch, wie Sie vermuten, wird gegen die Design-Ziele von Autofac (und MEF) arbeiten, einige der Vorteile zu beseitigen.

Im Idealfall müssen Sie einen Vertrag für Handler definieren, mit dem Sie alle Handler für einen Nachrichtentyp in einem einzigen Vorgang nachschlagen können.

Das typische Rezept wie folgt aussieht:

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

Handlers dann die entsprechende Schnittstelle implementieren:

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

... und wie so bei Build-Zeit registrieren lassen:

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

Um die Handler aufzurufen, definieren Sie einen Message Dispatcher-Vertrag:

interface IMessageDispatcher 
{ 
    void Dispatch(object message); 
} 

... und dann seine Umsetzung:

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); 
    } 
} 

..., die wie so registriert ist:

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

Die Komponente, die dann lösen/verwenden in den Nachrichten-Feeds werden IMessageDispatcher um die Nachrichten an die Handler zu bekommen.

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

Es gibt immer noch Möglichkeiten, dies ohne die Schnittstelle zu tun, aber alle verlassen sich auf eine Art von Vertrag zu schaffen, die eindeutig Handler für eine bestimmte Nachricht definiert (zB einen Delegierten.)

auf lange Sicht die Generisches Handler-Muster wird am einfachsten zu pflegen sein.

Hoffe das hilft, Nick.

Verwandte Themen