Инъекционная инъекция с использованием Azure WebJobs SDK?
Проблема заключается в том, что Azure WebJobs SDK поддерживает только публичные статические методы в качестве точек ввода задания, что означает, что не существует способа реализации вставки конструктора/свойства.
Я не могу найти что-либо об этой теме в официальной документации/ресурсах SDK WebJobs. Единственное решение, с которым я столкнулся, основано на шаблоне локатора обслуживания (антивируса), описанном в этом сообщении здесь.
Есть ли хороший способ использовать "правильную" инъекцию зависимостей для проектов на основе Azure WebJobs SDK?
Ответы
Ответ 1
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);
}
}
Ответ 2
Вот как я справился с использованием нового 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 в своей JobHostConfiguration, например
var serviceBusConfig = new ServiceBusConfiguration
{
ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);
Ответ 3
После запроса моего собственного вопроса о том, как обрабатывать область охвата... Я только что подошел к этому решению: я не думаю, что это идеально, но я на данный момент не может найти другого решения.
В моем примере я имею дело с 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));
}
}
Здесь я имею дело с Triggered webjobs.
Итак, у меня есть две зависимости:
-
Синтаксис:
public interface ISingletonDependency { }
public class SingletonDependency : ISingletonDependency { }
-
И еще один, который должен поддерживать только время срабатывания моей функции:
public class ScopedDependency : IScopedDependency, IDisposable
{
public void Dispose()
{
//Dispose what need to be disposed...
}
}
Итак, чтобы иметь процесс, который выполняется независимо от webjob. Я инкапсулировал свой процесс в класс:
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)
{
...
}
}
Итак, теперь, когда начинается webjob, мне нужно регистрировать зависимости в зависимости от их областей:
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);
}
}
}
Ответ 4
Я использовал пару шаблонов, которые полагаются на концепцию дочерних контейнеров/областей (в зависимости от терминологии вашего контейнера 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)
{
}
}