web-dev-qa-db-fra.com

Injection de dépendances à l'aide du SDK Azure WebJobs?

Le problème est que le SDK Azure WebJobs prend uniquement en charge les méthodes statiques publiques comme points d'entrée de travail, ce qui signifie qu'il n'y a aucun moyen d'implémenter l'injection de constructeur/propriété.

Je ne trouve rien sur ce sujet dans la documentation/les ressources officielles du SDK WebJobs. La seule solution que j'ai rencontrée est basée sur le modèle de localisateur de service (anti) décrit sur ce post ici .

Existe-t-il un bon moyen d'utiliser l'injection de dépendances "appropriée" pour les projets basés sur le SDK Azure WebJobs?

60
Milos Mrdovic

Le SDK Azure WebJobs prend désormais en charge les méthodes d'instance. La combinaison de cela avec un IJobActivator personnalisé vous permet d'utiliser DI.

Tout d'abord, créez le IJobActivator personnalisé qui peut résoudre un type de travail à l'aide de votre conteneur DI préféré:

public class MyActivator : IJobActivator
{
    private readonly IUnityContainer _container;

    public MyActivator(IUnityContainer container)
    {
        _container = container;
    }

    public T CreateInstance<T>()
    {
        return _container.Resolve<T>();
    }
}

Vous devez enregistrer cette classe à l'aide d'une JobHostConfiguration personnalisée:

var config = new JobHostConfiguration
{
    JobActivator = new MyActivator(myContainer)
};
var Host = new JobHost(config);

Ensuite, vous pouvez utiliser une classe simple avec des méthodes d'instance pour vos travaux (ici j'utilise la fonctionnalité d'injection de constructeur d'Unity):

public class MyFunctions
{
    private readonly ISomeDependency _dependency;

    public MyFunctions(ISomeDependency dependency)
    {
        _dependency = dependency;
    }

    public Task DoStuffAsync([QueueTrigger("queue")] string message)
    {
        Console.WriteLine("Injected dependency: {0}", _dependency);

        return Task.FromResult(true);
    }
}
89
Sander Molenkamp

C'est ainsi que j'ai géré la portée à l'aide du nouveau SDK. Utilisation de l'IJobactivator comme décrit par Alexander Molenkamp.

public class ScopedMessagingProvider : MessagingProvider
{
    private readonly ServiceBusConfiguration _config;
    private readonly Container _container;

    public ScopedMessagingProvider(ServiceBusConfiguration config, Container container)
        : base(config)
    {
        _config = config;
        _container = container;
    }

    public override MessageProcessor CreateMessageProcessor(string entityPath)
    {
        return new CustomMessageProcessor(_config.MessageOptions, _container);
    }

    private class CustomMessageProcessor : MessageProcessor
    {
        private readonly Container _container;

        public CustomMessageProcessor(OnMessageOptions messageOptions, Container container)
            : base(messageOptions)
        {
            _container = container;
        }

        public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
        {
            _container.BeginExecutionContextScope();
            return base.BeginProcessingMessageAsync(message, cancellationToken);

        }

        public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
        {
            var scope = _container.GetCurrentExecutionContextScope();
            if (scope != null)
            {
                scope.Dispose();
            }

            return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
        }
    }
}

Vous pouvez utiliser votre MessagingProvider personnalisé dans votre JobHostConfiguration comme

var serviceBusConfig = new ServiceBusConfiguration
{ 
    ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);
12
Niklas Arbin

Après avoir demandé à ma propre question comment gérer la portée ... Je viens de trouver cette solution: je ne pense pas que ce soit idéal mais je n'ai pas trouvé d'autre solution pour le moment .

Dans mon exemple, je traite avec ServiceBusTrigger.

Comme j'utilise SimpleInjector , l'implémentation de l'interface IJobActivator ressemble à ça:

public class SimpleInjectorJobActivator : IJobActivator
{
    private readonly Container _container;

    public SimpleInjectorJobActivator(Container container)
    {
        _container = container;
    }

    public T CreateInstance<T>()
    {
        return (T)_container.GetInstance(typeof(T));
    }
}

Ici, j'ai affaire à des webjobs déclenchés.

J'ai donc deux dépendances:

  • Un singleton:

    public interface ISingletonDependency { }
    
    public class SingletonDependency : ISingletonDependency { }
    
  • Et un autre qui n'a besoin de vivre que le temps où ma fonction est déclenchée:

    public class ScopedDependency : IScopedDependency, IDisposable
    {
        public void Dispose()
        {
             //Dispose what need to be disposed...
        }
    }
    

Donc, afin d'avoir un processus qui s'exécute indépendamment du webjob. J'ai encapsulé mon processus dans une classe:

public interface IBrokeredMessageProcessor
{
    Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token);
}

public class BrokeredMessageProcessor : IBrokeredMessageProcessor
{
    private readonly ISingletonDependency _singletonDependency;
    private readonly IScopedDependency _scopedDependency;

    public BrokeredMessageProcessor(ISingletonDependency singletonDependency, IScopedDependency scopedDependency)
    {
        _singletonDependency = singletonDependency;
        _scopedDependency = scopedDependency;
    }

    public async Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token)
    {
        ...
    }
}

Alors maintenant, lorsque le webjob démarre, je dois enregistrer mes dépendances en fonction de leurs étendues:

class Program
{
    private static void Main()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
        container.RegisterSingleton<ISingletonDependency, SingletonDependency>();
        container.Register<IScopedDependency, ScopedDependency>(Lifestyle.Scoped);
        container.Register<IBrokeredMessageProcessor, BrokeredMessageProcessor>(Lifestyle.Scoped);
        container.Verify();

        var config = new JobHostConfiguration
        {
            JobActivator = new SimpleInjectorJobActivator(container)
        };

        var servicebusConfig = new ServiceBusConfiguration
        {
            ConnectionString = CloudConfigurationManager.GetSetting("MyServiceBusConnectionString")
        };

        config.UseServiceBus(servicebusConfig);
        var Host = new JobHost(config);
        Host.RunAndBlock();
    }
}

Et c'est le travail déclenché:

  • N'ont qu'une seule dépendance: le conteneur IoC. Parce que cette classe fait partie de ma racine de composition, ça devrait aller.
  • Il gère la portée dans la fonction déclenchée.

    public class TriggeredJob
    {
        private readonly Container _container;
    
        public TriggeredJob(Container container)
        {
            _container = container;
        }
    
        public async Task TriggeredFunction([ServiceBusTrigger("queueName")] BrokeredMessage message, CancellationToken token)
        {
            using (var scope = _container.BeginExecutionContextScope())
            {
                var processor = _container.GetInstance<IBrokeredMessageProcessor>();
                await processor.ProcessAsync(message, token);
            }
        }
    }
    
11
Thomas

J'ai utilisé quelques modèles qui s'appuient sur le concept de conteneurs/étendues enfants (selon la terminologie de votre conteneur IoC de choix). Je ne sais pas lesquels les prennent en charge, mais je peux vous dire que StructureMap 2.6.x et AutoFac le font.

L'idée est de faire tourner une portée enfant pour chaque message entrant, d'injecter tout contexte unique à cette demande, de résoudre l'objet de niveau supérieur à partir de la portée enfant, puis d'exécuter votre processus.

Voici un code généralisé le montrant avec AutoFac. Il résout directement le conteneur, semblable à l'anti-modèle que vous essayez d'éviter, mais il a été isolé à un endroit.

Dans ce cas, il utilise un ServiceBusTrigger pour déclencher le travail, mais pourrait être n'importe quoi - un hôte de travail pourrait potentiellement en avoir une liste pour les différentes files d'attente/processus.

public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request)
{
   ProcessMessage(request);
}

Cette méthode est appelée par toutes les instances des méthodes ci-dessus. Il encapsule la création de la portée enfant dans un bloc using pour s'assurer que les choses sont nettoyées. Ensuite, tous les objets qui varieraient selon la demande et contiendraient le contexte utilisé par d'autres dépendances (informations utilisateur/client, etc.) seraient créés et injectés dans le conteneur enfant (dans cet exemple, IRequestContext). Enfin, le composant effectuant le travail serait résolu à partir du conteneur enfant.

private static void ProcessMessage<T>(T request) where T : IServiceBusRequest
{
    try
    {
        using (var childScope = _container.BeginLifetimeScope())
        {
            // create and inject things that hold the "context" of the message - user ids, etc

            var builder = new ContainerBuilder();
            builder.Register(c => new ServiceRequestContext(request.UserId)).As<IRequestContext>().InstancePerLifetimeScope();
            builder.Update(childScope.ComponentRegistry);

            // resolve the component doing the work from the child container explicitly, so all of its dependencies follow

            var thing = childScope.Resolve<ThingThatDoesStuff>();
            thing.Do(request);
        }
    }
    catch (Exception ex)
    {

    }
}
4
Veatch