使用Azure WebJobs SDK进行依赖注入?

Mil*_*vic 60 c# dependency-injection azure azure-webjobs

问题是Azure WebJobs SDK仅支持公共静态方法作为作业入口点,这意味着无法实现构造函数/属性注入.

我无法在官方WebJobs SDK文档/资源中找到有关此主题的任何内容.我遇到的唯一的解决方案是基于对这个职位描述的服务定位器(反)模式在这里.

是否有一种很好的方法可以为基于Azure WebJobs SDK的项目使用"正确的"依赖注入?

San*_*amp 89

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

您需要使用自定义JobHostConfiguration注册此类:

var config = new JobHostConfiguration
{
    JobActivator = new MyActivator(myContainer)
};
var host = new JobHost(config);
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用一个带有实例方法的简单类来完成您的工作(这里我使用的是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于触发的作业(不使用队列等),我们是否需要手动触发该函数才能使用作业激活器?`CallMethod(...)`? (3认同)
  • 我想指出,这只是来自v 1.0.1及以上库的支持. (2认同)
  • 传递到 MyActivator 构造函数中的 myContainer 到底是什么? (2认同)
  • 在此示例中,myContainer是IUnityContainer的一个实例,它是一个依赖注入框架.但是,您不需要使用Unity,相同的模式适用于其他依赖注入框架,如Ninject或Autofac. (2认同)

Nik*_*bin 12

这就是我使用新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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在JobHostConfiguration中使用自定义MessagingProvider

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


Tho*_*mas 11

在询问了我自己关于如何处理范围的问题之后...我刚刚达成了这个解决方案:我认为这不是理想的,但我暂时找不到任何其他解决方案.

在我的例子中,我正在处理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));
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我正在处理Triggered webjobs.

所以我有两个依赖项:

因此,为了拥有一个独立于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)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在当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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是触发的工作:

  • 只有一个依赖: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);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 即使实例不是一次性的,您也可能希望在整个请求中共享实例(例如DbContext). (2认同)

Ehs*_*edi 6

该问题的所有答案现在都已过时。使用最新的软件包,您可以轻松地获得开箱即用的构造函数注入。只需要两步:

  1. 创建事件处理函数作为非静态类中的实例方法。我们来给班级打电话吧QueueFunctions

  2. 将您的班级添加到服务列表中。

     builder.ConfigureServices(services =>
     {
         // Add 
         // dependencies
         // here
    
         services.AddScoped<QueueFunctions>();
     });
    
    Run Code Online (Sandbox Code Playgroud)

现在,您将能够通过构造函数注入依赖项。


Vea*_*tch 5

我使用了一些依赖于子容器/范围概念的模式(取决于您选择的 IoC 容器的术语)。不确定哪些支持它,但我可以告诉您 StructureMap 2.6.x 和 AutoFac 支持。

这个想法是为每条传入的消息启动一个子作用域,注入该请求特有的任何上下文,从子作用域解析顶级对象,然后运行您的流程。

下面是一些用 AutoFac 展示它的通用代码。它确实从容器中进行直接解析,类似于您试图避免的反模式,但它被隔离到一个地方。

在这种情况下,它使用 ServiceBusTrigger 来触发作业,但可以是任何东西 - 作业主机可能有不同队列/进程的这些触发器的列表。

public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request)
{
   ProcessMessage(request);
}
Run Code Online (Sandbox Code Playgroud)

该方法由上述方法的所有实例调用。它将子作用域的创建包装在 using 块中,以确保清理干净。然后,将创建任何根据请求而变化并包含其他依赖项使用的上下文(用户/客户端信息等)的对象并将其注入到子容器中(在本例中为 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)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)