web-dev-qa-db-fra.com

Autofac - résolution des paramètres d'exécution sans avoir à passer le conteneur

J'ai une classe "ServiceHelper" plus simple qui prend deux paramètres dans le constructeur:

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)

(L'encapsuleur générique ILogger pour NLog qu'Autofac fournit très bien, et le serviceName est le nom d'un service Windows à contrôler que je dois fournir lors de l'exécution.)

J'ai du mal à comprendre comment créer de nouvelles instances de cette classe au moment de l'exécution en transmettant différents noms de service à l'aide d'Autofac. Quelque chose comme ça ne fonctionne pas bien sûr car j'ai besoin de spécifier différents noms de service lors de l'exécution:

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();

D'après ce que j'ai lu, c'est une mauvaise habitude de faire circuler le conteneur et d'appeler Resolve manuellement à droite (l'anti-modèle de localisation de service que l'AutoFac met en garde), ou est-ce? Si je faisais ça, je pourrais faire

container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));

Mais pour aller jusque-là, je ne sais pas trop comment obtenir Autofac pour injecter le conteneur dans les classes, il aurait juste besoin de s'enregistrer exactement, comme ça? Et puis mes classes ont-elles besoin d'un IContainer dans leurs constructeurs? (Ceci est dans un service C # utilisant l'injection de constructeur)

builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();

J'ai aussi lu des articles sur les usines de délégués, mais cela ne semble pas m'empêcher de devoir faire circuler le conteneur.

Vraiment la plupart de mes classes qui utilisent le ServiceHelper, ont juste besoin de 1 ou 2 ServiceHelpers pour des noms de service spécifiques, donc ce n'est pas comme si je fais des milliers avec des paramètres serviceName inattendus, cela me fait un peu mal à la tête.

21
Jeremy

Oui, faire passer le conteneur partout est un anti-motif.

Vous pouvez l'éviter en utilisant une usine comme celle-ci:

(note: tout le code de cette réponse n'est pas testé, j'écris ceci dans un éditeur de texte sur une machine sans Visual Studio)

public interface IServiceHelperFactory
{
    IServiceHelper CreateServiceHelper(string serviceName);
}

public class ServiceHelperFactory : IServiceHelperFactory
{
    private IContainer container;

    public ServiceHelperFactory(IContainer container)
    {
        this.container = container;
    }

    public IServiceHelper CreateServiceHelper(string serviceName)
    {
        return container.Resolve<ServiceHelper>(new NamedParameter("serviceName", serviceName));
    }
}

Au démarrage, vous enregistrez le ServiceHelperFactory dans Autofac, comme tout le reste:

builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();

Ensuite, lorsque vous avez besoin d'un ServiceHelper ailleurs, vous pouvez obtenir l'usine par injection de constructeur:

public class SomeClass : ISomeClass
{
    private IServiceHelperFactory factory;

    public SomeClass(IServiceHelperFactory factory)
    {
        this.factory = factory;
    }

    public void ThisMethodCreatesTheServiceHelper()
    {
        var helper = this.factory.CreateServiceHelper("some service name");
    }
}

En créant l'usine elle-même via l'injection de constructeur avec Autofac, vous vous assurez que l'usine connaît le conteneur, sans avoir à le faire passer vous-même.

J'admets qu'à première vue, cette solution ne semble pas très différente de celle de faire passer le conteneur directement. Mais l'avantage est que votre application est toujours découplée du conteneur - le seul endroit où le conteneur est connu (sauf le démarrage) est à l'intérieur de l'usine.


MODIFIER:

OK, j'ai oublié. Comme je l'ai dit ci-dessus, j'écris ceci sur une machine sans Visual Studio, donc je ne suis pas en mesure de tester mon exemple de code.
Maintenant que j'ai lu votre commentaire, je me souviens que j'ai eu un problème similaire lorsque j'ai utilisé Autofac et essayé d'enregistrer le conteneur lui-même.

Mon problème était que je devais enregistrer le conteneur dans le générateur.
Mais pour obtenir l'instance de conteneur à enregistrer, j'ai dû appeler builder.Build()... qui crée le conteneur, ce qui signifie que je ne peux pas enregistrer de trucs dans le générateur par la suite.
Je ne me souviens pas du message d'erreur que j'ai reçu, mais je suppose que vous avez le même problème maintenant.

La solution que j'ai trouvée consistait à créer un deuxième générateur, à y enregistrer le conteneur , puis à utiliser le deuxième générateur pour mettre à jour le seul et unique conteneur .

Voici mon code de travail d'un de mes projets open source:

Au démarrage, j'enregistre le conteneur: :

var builder = new ContainerBuilder();

// register stuff here

var container = builder.Build();

// register the container
var builder2 = new ContainerBuilder();
builder2.RegisterInstance<IContainer>(container);
builder2.Update(container);

... qui est ensuite utilisé par un WindowService pour créer de nouvelles fenêtres WPF :

public class WindowService : IWindowService
{
    private readonly IContainer container;

    public WindowService(IContainer container)
    {
        this.container = container;
    }

    public T GetWindow<T>() where T : MetroWindow
    {
        return (T)this.container.Resolve<T>();
    }
}
21
Christian Specht

J'ai emprunté la voie de l'approche ci-dessus et cela fonctionne très bien, mais j'ai trouvé impossible de faire des tests unitaires car la méthode "Resolve <>" dans IContainer est une méthode d'extension. De plus, il ne s'est jamais vraiment senti "bien" avec toutes les discussions sur le fait de ne pas passer votre conteneur.

Je suis retourné à la planche à dessin et j'ai trouvé la manière "correcte" d'instancier des objets en utilisant Autofac Delegate Factories http://docs.autofac.org/en/latest/advanced/delegate-factories.html

7
raterus

La résolution doit se produire uniquement pour les objets de composition racine. L'appel de résolution est presque identique à la "nouvelle" création d'un objet, qui est un test d'odeur. Il y a des moments où la résolution est dynamique et ne peut être déterminée qu'à la volée, mais la plupart des dépendances sont déterministes et peuvent être enregistrées à l'avance. Comment faire cela avec Autofac est le défi. La réponse attribuée par @Christian Specht est une bonne réponse, mais elle suppose que tout est déterminé au moment de l'exécution.

Pour définir une chaîne de dépendances au moment du design, voir SO topic Enregistrement de chaîne de sous-dépendances Autofac ...

3
barrypicker