2016-06-15 4 views
0

Lets assumer ces classes/interfaces:Obtenez toutes les variantes d'enregistrement AsClosedTypesOf du Autofac Builder

public interface ICommand 
{ 
} 

public class SomeCommand : ICommand 
{ 
} 

public interface ICommandHandler<T> where T : ICommand 
{ 
    void Handle(T arg); 
} 

public class SomeCommandHandler : ICommandHandler<SomeCommand> 
{ 
    void Handle(SomeCommand arg){ /* do something */ } 
} 

public interface ICommandBus 
{ 
    void RegisterHandler<T>(T t) where T : ICommandHandler<T>; 
    void RegisterHandlerByParam<T2>(ICommandHandler<T2> t2) where T2 : ICommand; 
    void RegisterHandlerMethod<T3>(Action<T3> action) where T3 : ICommand 
} 

public class TheCommandBus : ICommandBus 
{ 
    // implements ICommandBus ... 
} 

Je veux enregistrer toutes les implémentations de ICommandHandler <> automatiquement. Toutes les variantes (Register *) sont des solutions valides même si je préfère le paramètre Action alors qu'il est plus flexible et n'a pas de dépendance à l'interface Handler (juste délégué d'action).

Autofac a la capacité d'enregistrer les types basés sur l'analyse de l'assemblage et enregistrer les implémentations trouvées d'une interface générique, par exemple:

builder.RegisterAssemblyTypes(Assembly.LoadFrom("MyAssembly.dll")) 
     .AsClosedTypesOf(typeof(ICommandHandler<>)); 

J'ai donc toutes les implémentations enregistrées. Maintenant, je dois les enregistrer tous automatiquement à TheCommandBus. Comment faire cela?

Je peux le faire manuellement en ajoutant ces lignes (par exemple lors OnActivated):

builder.RegisterType<TheCommandBus>().As<ICommandBus>().OnActivated(args => 
     { 
      // now I need to list all implementations here!!! please, no... 
      args.Instance.RegisterHandler<ICommandHandler<SomeCommand>>(args.Context.Resolve<ICommandHandler<SomeCommand>>()); 

      // does not look better to me than before ... 
      args.Instance.RegisterHandlerByParam<SomeCommand>(args.Context.Resolve<ICommandHandler<SomeCommand>>()) 

      // uses delegate for, but still need to list all variants 
      args.Instance.RegisterHandlerMethod<SomeCommand>(args.Context.Resolve<ICommandHandler<SomeCommand>>().Handle) 
     }); 

Si je veux utiliser un tel type dans une expression lambda lors de l'inscription, j'ai le problème, que je dois identifier le type concret, comme pour cet exemple sur le processus d'activation d'un autre composant. Mais je ne veux pas les lister tous manuellement ... je veux quelque chose comme ça automatiquement.

Comment attraper toutes les implémentations ICommandHandler ET les enregistrer automatiquement avec la méthode Register *?

Edit:

Une autre variante consiste à étendre la classe SomeCommandHandler s'enregistrer quand résolu dans son constructeur:

public SomeCommandHandler(ICommandBus commandBus) 
    { 
     // and register here, for example 
     commandBus.RegisterHandlerbyParam(this); 
    } 

De cette façon, je dois fournir AutoActivate() au résultat de l'enregistrement de AsClosedTypesOf . (une solution possible, mais maintenant les «gestionnaires» ont deux responsabilités ... l'enregistrement et la manipulation)

Répondre

1

Ceci est un problème intéressant et délicat. Les génériques ajoutent définitivement à cette complexité car aller non-générique serait une simple résolution IEnumerable<T>.

Mais ... Je pense que je peux vous aider.

Vous profiterez de ...

  • L'événement OnRegistered en RegisterAssemblyTypes afin que vous puissiez regarder ce qui a été enregistré. Le OnActivating event pour le bus afin que vous puissiez faire l'enregistrement des gestionnaires.
  • Fermetures pour porter la liste des types de gestionnaires enregistrés dans l'événement OnActivating.
  • Une réflexion fantaisie-schmancy pour créer la (les) version (s) générique (s) fermée (s) de la méthode RegisterHandler sur le bus.

Voici un exemple complet illustrant comment procéder. Note J'ai dû changer l'interface ICommandBus pour RegisterHandler un peu car il ne compilerait pas pour moi sous la forme initialement indiquée, mais vous devriez être capable de vous adapter au besoin. J'ai couru ceci dans ScriptCs pour vérifier.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using Autofac; 

public interface ICommand { } 
public class CommandOne : ICommand { } 
public class CommandTwo : ICommand { } 

public interface ICommandHandler<T> where T : ICommand 
{ 
    void Handle(T arg); 
} 

public class CommandOneHandler : ICommandHandler<CommandOne> 
{ 
    public void Handle(CommandOne arg) { } 
} 

public class CommandTwoHandler : ICommandHandler<CommandTwo> 
{ 
    public void Handle(CommandTwo arg) { } 
} 

public interface ICommandBus 
{ 
    IEnumerable<object> Handlers { get; } 
    void RegisterHandler<TCommand, THandler>(THandler handler) 
    where THandler : ICommandHandler<TCommand> 
    where TCommand : ICommand; 
} 

public class CommandBus : ICommandBus 
{ 
    private readonly List<object> _handlers = new List<object>(); 

    public IEnumerable<object> Handlers 
    { 
    get 
    { 
     return this._handlers; 
    } 
    } 

    public void RegisterHandler<TCommand, THandler>(THandler handler) 
    where THandler : ICommandHandler<TCommand> 
    where TCommand : ICommand 
    { 
    this._handlers.Add(handler); 
    } 
} 

var builder = new ContainerBuilder(); 

// Track the list of registered command types. 
var registeredHandlerTypes = new List<Type>(); 
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) 
    .AsClosedTypesOf(typeof(ICommandHandler<>)) 
    .OnRegistered(e => registeredHandlerTypes.Add(e.ComponentRegistration.Activator.LimitType)); 

// Initialize the bus by registering handlers on activating. 
builder.RegisterType<CommandBus>() 
    .As<ICommandBus>() 
    .OnActivating(e => { 
    foreach(var handlerType in registeredHandlerTypes) 
    { 
     // Due to the generic method, some crazy reflection happens. 
     // First, get ICommandHandler<T> interface. 
     var handlerInterfaceType = handlerType.GetInterface("ICommandHandler`1"); 
     // Grab the <T> from the ICommandHandler<T>. 
     var commandType = handlerInterfaceType.GetGenericArguments()[0]; 
     // Build the closed generic version of RegisterHandler<TCommand, THandler>. 
     var registerMethod = typeof(ICommandBus).GetMethod("RegisterHandler").MakeGenericMethod(commandType, handlerType); 
     // Call the closed generic RegisterHandler<TCommand, THandler> to register the handler. 
     registerMethod.Invoke(e.Instance, new object[] { e.Context.Resolve(handlerInterfaceType) }); 
    } 
    }) 
    .SingleInstance(); 

var container = builder.Build(); 
using(var scope = container.BeginLifetimeScope()) 
{ 
    var bus = scope.Resolve<ICommandBus>(); 
    foreach(var t in bus.Handlers) 
    { 
    // List the handler types registered. 
    Console.WriteLine(t.GetType()); 
    } 
} 
+0

Belle solution. THX. Je donnerais un 2ème upvote pour "réflexion de fantaisie-schmancy" :-D – Beachwalker