使用 Azure WebJobs SDK 进行依赖注入?
Dependency injection using Azure WebJobs SDK?
问题是 Azure WebJobs SDK 仅支持 public 静态方法作为作业入口点,这意味着无法实现 constructor/property 注入。
我无法在官方 WebJobs SDK documentation/resources 中找到有关此主题的任何信息。我遇到的唯一解决方案是基于此 post here.
中描述的服务定位器(反)模式
对于基于Azure WebJobs SDK 的项目,有没有好的方法使用"proper" 依赖注入?
我使用了几个依赖子概念的模式 containers/scopes(取决于您选择的 IoC 容器的术语)。不确定哪些支持它,但我可以告诉你 StructureMap 2.6.x 和 AutoFac 支持。
想法是为每个传入的消息启动一个子范围,注入该请求唯一的任何上下文,从子范围解析顶级对象,然后 运行 您的进程。
下面是一些使用 AutoFac 显示的通用代码。它确实从容器直接解析,类似于您试图避免的反模式,但它被隔离到一个地方。
在这种情况下,它使用 ServiceBusTrigger 来触发作业,但可以是任何东西 - 作业宿主可能有一个列表,用于不同的 queues/processes。
public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request)
{
ProcessMessage(request);
}
上述方法的所有实例都会调用此方法。它将子作用域的创建包装在一个 using 块中,以确保清理干净。然后,将创建任何因请求而异并包含其他依赖项使用的上下文(user/client 信息等)的对象,并将其注入子容器(在本例中为 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)
{
}
}
Azure WebJobs SDK 现在支持实例方法。将此与自定义 IJobActivator 结合使用,您可以使用 DI。
首先,创建可以使用您最喜欢的 DI 容器解析作业类型的自定义 IJobActivator:
public class MyActivator : IJobActivator
{
private readonly IUnityContainer _container;
public MyActivator(IUnityContainer container)
{
_container = container;
}
public T CreateInstance<T>()
{
return _container.Resolve<T>();
}
}
您需要使用自定义 JobHostConfiguration 注册此 class:
var config = new JobHostConfiguration
{
JobActivator = new MyActivator(myContainer)
};
var host = new JobHost(config);
然后,您可以使用带有实例方法的简单 class 来完成您的作业(这里我使用的是 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);
}
}
这就是我使用新 SDK 处理范围界定的方式。按照 Alexander Molenkamp 的描述使用 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);
}
}
}
您可以在 JobHostConfiguration 中使用自定义 MessagingProvider,例如
var serviceBusConfig = new ServiceBusConfiguration
{
ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);
在询问了我的 own question 如何处理范围界定之后......我刚刚想到了这个解决方案:我认为这不是理想的,但我找不到任何其他解决方案时刻.
在我的示例中,我正在处理 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...
}
}
所以为了有一个 运行 独立于 webjob 的进程。我已将我的过程封装到 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)
{
...
}
}
所以现在当 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 容器。因为这个class是我作文根的一部分,应该没问题。
它将作用域处理到触发函数中。
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);
}
}
}
问题的所有答案现已过时。使用最新的软件包,您可以轻松地立即获得构造函数注入。只需两步:
在非静态 class 中创建事件处理函数作为实例方法。我们称 class QueueFunctions
.
将您的 class 添加到服务列表。
builder.ConfigureServices(services =>
{
// Add
// dependencies
// here
services.AddScoped<QueueFunctions>();
});
现在,您将能够通过构造函数注入依赖项。
问题是 Azure WebJobs SDK 仅支持 public 静态方法作为作业入口点,这意味着无法实现 constructor/property 注入。
我无法在官方 WebJobs SDK documentation/resources 中找到有关此主题的任何信息。我遇到的唯一解决方案是基于此 post here.
中描述的服务定位器(反)模式对于基于Azure WebJobs SDK 的项目,有没有好的方法使用"proper" 依赖注入?
我使用了几个依赖子概念的模式 containers/scopes(取决于您选择的 IoC 容器的术语)。不确定哪些支持它,但我可以告诉你 StructureMap 2.6.x 和 AutoFac 支持。
想法是为每个传入的消息启动一个子范围,注入该请求唯一的任何上下文,从子范围解析顶级对象,然后 运行 您的进程。
下面是一些使用 AutoFac 显示的通用代码。它确实从容器直接解析,类似于您试图避免的反模式,但它被隔离到一个地方。
在这种情况下,它使用 ServiceBusTrigger 来触发作业,但可以是任何东西 - 作业宿主可能有一个列表,用于不同的 queues/processes。
public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request)
{
ProcessMessage(request);
}
上述方法的所有实例都会调用此方法。它将子作用域的创建包装在一个 using 块中,以确保清理干净。然后,将创建任何因请求而异并包含其他依赖项使用的上下文(user/client 信息等)的对象,并将其注入子容器(在本例中为 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)
{
}
}
Azure WebJobs SDK 现在支持实例方法。将此与自定义 IJobActivator 结合使用,您可以使用 DI。
首先,创建可以使用您最喜欢的 DI 容器解析作业类型的自定义 IJobActivator:
public class MyActivator : IJobActivator
{
private readonly IUnityContainer _container;
public MyActivator(IUnityContainer container)
{
_container = container;
}
public T CreateInstance<T>()
{
return _container.Resolve<T>();
}
}
您需要使用自定义 JobHostConfiguration 注册此 class:
var config = new JobHostConfiguration
{
JobActivator = new MyActivator(myContainer)
};
var host = new JobHost(config);
然后,您可以使用带有实例方法的简单 class 来完成您的作业(这里我使用的是 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);
}
}
这就是我使用新 SDK 处理范围界定的方式。按照 Alexander Molenkamp 的描述使用 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);
}
}
}
您可以在 JobHostConfiguration 中使用自定义 MessagingProvider,例如
var serviceBusConfig = new ServiceBusConfiguration
{
ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);
在询问了我的 own question 如何处理范围界定之后......我刚刚想到了这个解决方案:我认为这不是理想的,但我找不到任何其他解决方案时刻.
在我的示例中,我正在处理 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... } }
所以为了有一个 运行 独立于 webjob 的进程。我已将我的过程封装到 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)
{
...
}
}
所以现在当 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 容器。因为这个class是我作文根的一部分,应该没问题。
它将作用域处理到触发函数中。
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); } } }
问题的所有答案现已过时。使用最新的软件包,您可以轻松地立即获得构造函数注入。只需两步:
在非静态 class 中创建事件处理函数作为实例方法。我们称 class
QueueFunctions
.将您的 class 添加到服务列表。
builder.ConfigureServices(services => { // Add // dependencies // here services.AddScoped<QueueFunctions>(); });
现在,您将能够通过构造函数注入依赖项。