使用Azure WebJobs SDK进行dependency injection
问题是,Azure WebJobs SDK只支持公共静态方法作为入口点,这意味着没有办法实现构造函数/属性注入。
我无法在官方WebJobs SDK文档/资源中find有关此主题的任何内容。 我遇到的唯一解决scheme是基于这篇文章中描述的服务定位器(反)模式。
有没有一种很好的方法来使用基于Azure WebJobs SDK的项目的“适当的”依赖项注入?
Azure WebJobs SDK现在支持实例方法。 将它与定制的IJobActivator结合使用,可以使用DI。
首先,创build可以使用您最喜爱的DI容器来parsing作业types的自定义IJobActivator:
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的构造函数注入function):
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); } }
这就是我使用新SDK处理范围的方式。 如Alexander Molenkamp所述使用IJobacter活化剂。
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); } } }
你可以在你的JobHostConfiguration中使用你自定义的MessagingProvider
var serviceBusConfig = new ServiceBusConfiguration { ConnectionString = config.ServiceBusConnectionString }; serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container); jobHostConfig.UseServiceBus(serviceBusConfig);
在问自己关于如何处理范围的问题后,我刚刚提出了这个解决scheme:我不认为这是理想的,但目前我找不到任何其他解决scheme。
在我的例子中,我正在处理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)); } }
在这里,我正在处理触发的webjobs。
所以我有两个依赖关系:
-
单身:
public interface ISingletonDependency { } public class SingletonDependency : ISingletonDependency { }
-
而另一个需要生活只有我的function被触发:
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); } } }
我已经使用了一些依赖于子容器/范围概念的模式(取决于您select的IoC容器的术语)。 不知道哪些支持它,但我可以告诉你,StructureMap 2.6.x和AutoFac做。
这个想法是为每个消息join一个子范围,注入任何对该请求唯一的上下文,从子范围parsing顶级对象,然后运行你的过程。
以下是一些通用代码,用AutoFac显示它。 它确实从容器中做出了一个直接的解决scheme,类似于你想要避免的反模式,但是它被隔离在一个地方。
在这种情况下,它使用ServiceBusTrigger来启动作业,但可能是任何东西 – 作业主机可能有不同的队列/进程的列表。
public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request) { ProcessMessage(request); }
这个方法被上述方法的所有实例调用。 它将创build子范围包含在一个使用块中以确保事物被清理。 然后,将创build任何会根据请求而变化并包含其他依赖项(用户/客户端信息等)使用的上下文的对象,并将其注入到子容器(在本例中为IRequestContext)中。 最后,执行该工作的组件将从子容器中parsing出来。
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) { } }