dependency injection usando o SDK do Azure WebJobs?

O problema é que o SDK do Azure WebJobs oferece suporte apenas a methods estáticos públicos como pontos de input de trabalho, o que significa que não há como implementar a injeção de construtor / propriedade.

Eu não consigo encontrar nada sobre este tópico na documentação / resources oficiais do WebJobs SDK. A única solução que encontrei é baseada no localizador de serviço (anti) padrão descrito neste post aqui .

Existe uma boa maneira de usar injeção de dependência “adequada” para projetos baseados no SDK do WebJobs do Azure?

O WebKabs do Azure Web SDK agora oferece suporte a methods de instância. Combinar isso com um personalizado IJobActivator permite que você use DI.

Primeiro, crie o IJobActivator personalizado que pode resolver um tipo de trabalho usando seu contêiner DI favorito:

public class MyActivator : IJobActivator { private readonly IUnityContainer _container; public MyActivator(IUnityContainer container) { _container = container; } public T CreateInstance() { return _container.Resolve(); } } 

Você precisa registrar essa class usando um JobHostConfiguration personalizado:

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

Em seguida, você pode usar uma class simples com methods de instância para seus trabalhos (aqui estou usando o recurso de injeção de construtor do 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); } } 

É assim que eu lidei com o escopo usando o novo SDK. Usando o IJobactivator como descrito por 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 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); } } } 

Você pode usar seu MessagingProvider personalizado em sua JobHostConfiguration como

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

Depois de fazer minha própria pergunta sobre como lidar com o escopo … Acabei de chegar a essa solução: não acho isso ideal, mas não consegui encontrar nenhuma outra solução no momento.

No meu exemplo, estou lidando com o ServiceBusTrigger.

Como estou usando o SimpleInjector , a implementação da interface do IJobActivator se parece com isso:

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

Aqui, estou lidando com webjobs acionados.

Então eu tenho duas dependencies:

  • Um singleton:

     public interface ISingletonDependency { } public class SingletonDependency : ISingletonDependency { } 
  • E outro que precisa viver apenas o tempo que minha function é acionada:

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

Então, para ter um processo que seja executado independentemente do webjob. Eu encapsulei meu processo em uma class:

 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) { ... } } 

Então, agora, quando o webjob começar, preciso registrar minhas dependencies dependendo de seus escopos:

 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 { ConnectionString = CloudConfigurationManager.GetSetting("MyServiceBusConnectionString") }; config.UseServiceBus(servicebusConfig); var host = new JobHost(config); host.RunAndBlock(); } } 

E este é o trabalho desencadeado:

  • Só tem uma dependência: o contêiner IoC. Como essa class faz parte da raiz da minha composição, ela deve estar ok.
  • Ele manipula o escopo na function acionada.

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

Eu usei alguns padrões que dependem do conceito de contêineres / escopos filho (dependendo da terminologia do seu contêiner de IoC de sua escolha). Não tenho certeza quais suportam, mas posso dizer-lhe que StructureMap 2.6.xe AutoFac fazer.

A ideia é criar um escopo filho para cada mensagem, injetar qualquer contexto exclusivo para essa solicitação, resolver o object de nível superior do escopo filho e, em seguida, executar o processo.

Aqui está um código generalizado mostrando-o com o AutoFac. Ele faz uma resolução direta do contêiner, semelhante ao antipadrão que você está tentando evitar, mas está isolado em um único lugar.

Neste caso, ele está usando um ServiceBusTrigger para triggersr o trabalho, mas poderia ser qualquer coisa – um host de trabalho poderia ter uma lista deles para as diferentes filas / processos.

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

Este método é chamado por todas as instâncias dos methods acima. Ele envolve a criação do escopo filho em um bloco de uso para garantir que as coisas sejam limpas. Em seguida, quaisquer objects que variem por solicitação e contenham contexto usado por outras dependencies (informações de usuário / cliente, etc) serão criados e injetados no contêiner filho (neste exemplo, o IRequestContext). Finalmente, o componente que faz o trabalho seria resolvido a partir do contêiner filho.

 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) { } } 
    Intereting Posts