Внедрение зависимостей с помощью Azure WebJobs SDK?

Проблема заключается в том, что Azure WebJobs SDK поддерживает только общедоступные статические методы в качестве точек входа в задание, что означает, что нет способа реализовать внедрение конструктора / свойства.

Я не могу найти что-либо по этой теме в официальной документации / ресурсах WebJobs SDK. Единственное решение, с которым я столкнулся, основано на шаблоне поиска служб (анти), описанном здесь.

Есть ли хороший способ использовать "правильное" внедрение зависимостей для проектов на основе Azure WebJobs SDK?

5 ответов

Решение

Azure WebJobs SDK теперь поддерживает методы экземпляров. Комбинируя это с пользовательским IJobActivator, вы можете использовать DI.

Сначала создайте пользовательский IJobActivator, который может разрешать тип задания, используя ваш любимый DI-контейнер:

public class MyActivator : IJobActivator
{
    private readonly IUnityContainer _container;

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

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

Вам нужно зарегистрировать этот класс с помощью пользовательского JobHostConfiguration:

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

Затем вы можете использовать простой класс с методами экземпляра для ваших заданий (здесь я использую функцию внедрения конструкторов 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);
    }
}

Задав собственный вопрос о том, как справиться со сферой охвата... Я только что пришел к этому решению: я не думаю, что это идеально, но на данный момент я не мог найти никакого другого решения.

В моем примере я имею дело с ServiceBusTrigger.

Поскольку я использую SimpleInjector, реализация интерфейса IJobActivator выглядит следующим образом:

public class SimpleInjectorJobActivator : IJobActivator
{
    private readonly Container _container;

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

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

Здесь я имею дело с запущенными веб-заданиями.

Итак, у меня есть две зависимости:

  • Синглтон:

    public interface ISingletonDependency { }
    
    public class SingletonDependency : ISingletonDependency { }
    
  • И еще один, который должен жить только время, когда моя функция запущена:

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

Таким образом, чтобы иметь процесс, который запускается независимо от веб-задания. Я инкапсулировал свой процесс в класс:

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

Итак, теперь, когда начинается веб-работа, мне нужно зарегистрировать свои зависимости в зависимости от их области:

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

И это вызванная работа:

  • Есть только одна зависимость: контейнер IoC. Поскольку этот класс является частью моего корня композиции, все должно быть в порядке.
  • Это обрабатывает область в вызванную функцию.

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

Вот как я справился с областью видимости, используя новый SDK. Использование IJobactivator, как описано Александром Моленкампом.

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

Вы можете использовать свой собственный MessagingProvider в своей конфигурации JobHost, например:

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

Все ответы на вопрос уже устарели. Используя последние пакеты, вы можете легко получить внедрение конструктора прямо из коробки. Требуется всего два шага:

  1. Создайте функцию обработчика событий как метод экземпляра в нестатическом классе. Давайте назовем класс .

  2. Добавьте свой класс в список услуг.

             builder.ConfigureServices(services =>
     {
         // Add 
         // dependencies
         // here
    
         services.AddScoped<QueueFunctions>();
     });
    

Теперь вы сможете внедрять зависимости через конструктор.

Я использовал пару шаблонов, которые основаны на концепции дочерних контейнеров / областей (в зависимости от терминологии выбранного вами контейнера IoC). Не уверен, какие из них поддерживают, но я могу сказать вам, что StructureMap 2.6.x и AutoFac делают.

Идея состоит в том, чтобы ускорить дочернюю область для каждого входящего сообщения, внедрить любой контекст, уникальный для этого запроса, разрешить объект верхнего уровня из дочерней области, а затем запустить ваш процесс.

Вот некоторый обобщенный код, показывающий это с помощью AutoFac. Он выполняет прямое разрешение из контейнера, подобно тому, как вы пытаетесь избежать анти-паттерна, но он изолирован в одном месте.

В этом случае он использует ServiceBusTrigger для запуска задания, но может быть чем угодно - у узла задания может быть список их для разных очередей / процессов.

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

Этот метод вызывается всеми экземплярами вышеуказанных методов. Он оборачивает создание дочерней области в блок использования, чтобы убедиться, что все очищено. Затем любые объекты, которые будут варьироваться в зависимости от запроса и содержать контекст, используемый другими зависимостями (информация о пользователе / ​​клиенте и т. Д.), Будут созданы и внедрены в дочерний контейнер (в этом примере, IRequestContext). Наконец, компонент, выполняющий работу, будет разрешен из дочернего контейнера.

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

    }
}
Другие вопросы по тегам