Injection de dépendance à l’aide d’Azure WebJobs SDK?

Le problème est que le SDK Azure WebJobs ne prend en charge que les méthodes statiques publiques en tant que points d’entrée de travail, ce qui signifie qu’il est impossible d’implémenter une injection 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 trouvée est basée sur le modèle de localisateur de services (anti) décrit dans cet article ici .

Existe-t-il un bon moyen d’utiliser l’dependency injection “appropriée” pour les projets basés sur Azure WebJobs SDK?

Azure WebJobs SDK prend désormais en charge les méthodes d’instance. En combinant ceci avec un IJobActivator personnalisé, vous pouvez utiliser DI.

Tout d’abord, créez l’IJobActivator personnalisé capable de 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() { return _container.Resolve(); } } 

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 de Unity):

 public class MyFunctions { private readonly ISomeDependency _dependency; public MyFunctions(ISomeDependency dependency) { _dependency = dependency; } public Task DoStuffAsync([QueueTrigger("queue")] ssortingng message) { Console.WriteLine("Injected dependency: {0}", _dependency); return Task.FromResult(true); } } 

C’est comme ça que j’ai géré la scope en utilisant le nouveau SDK. En utilisant le IJobactivator tel que 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(ssortingng 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 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 { ConnectionSsortingng = config.ServiceBusConnectionSsortingng }; serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container); jobHostConfig.UseServiceBus(serviceBusConfig); 

Après avoir posé ma propre question sur la manière de gérer la scope … Je viens juste de trouver cette solution: je ne pense pas que ce soit idéal, mais je ne trouve aucune autre solution pour le moment.

Dans mon exemple, je traite de ServiceBusTrigger.

Comme j’utilise SimpleInjector , l’implémentation de l’interface IJobActivator ressemble à ceci:

 public class SimpleInjectorJobActivator : IJobActivator { private readonly Container _container; public SimpleInjectorJobActivator(Container container) { _container = container; } public T CreateInstance() { return (T)_container.GetInstance(typeof(T)); } } 

Ici, je traite 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, pour avoir un processus qui fonctionne 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) { ... } } 

Donc, maintenant que le webjob commence, 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(); container.Register(Lifestyle.Scoped); container.Register(Lifestyle.Scoped); container.Verify(); var config = new JobHostConfiguration { JobActivator = new SimpleInjectorJobActivator(container) }; var servicebusConfig = new ServiceBusConfiguration { ConnectionSsortingng = CloudConfigurationManager.GetSetting("MyServiceBusConnectionSsortingng") }; config.UseServiceBus(servicebusConfig); var host = new JobHost(config); host.RunAndBlock(); } } 

Et c’est le travail déclenché:

  • N’avoir 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 scope 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(); await processor.ProcessAsync(message, token); } } } 

J’ai utilisé quelques modèles qui reposent sur le concept de conteneurs / scopes enfants (en fonction de la terminologie de votre conteneur IoC de choix). Je ne suis pas sûr de ceux qui le supportent, mais je peux vous dire que StructureMap 2.6.x et AutoFac le font.

L’idée est de lancer une scope enfant pour chaque message entrant, d’injecter tout contexte propre à cette demande, de résoudre l’object de niveau supérieur à partir de la scope enfant, puis d’exécuter votre processus.

Voici un code généralisé le montrant avec AutoFac. Il fait une résolution directe à partir du conteneur, similaire à 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 cela peut être n’importe quoi: un hôte de travail peut 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 scope enfant dans un bloc using pour s’assurer que les choses sont nettoyées. Ensuite, tous les objects 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 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().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(); thing.Do(request); } } catch (Exception ex) { } }