3

I've read and asked some questions on how to use DI using WebJob and especially Triggered Webjobs.

I still try to figure out how to integrate gracefully DI in triggered webjobs and @Steven asked me a good question :

Isn't there a way to wrap the execution of your service in some scope? For instance, MVC and Web API have an IDependencyScope abstraction for this. This notifies the starting and ending of a request. To be able to do scoping, you either need to have such interception point or you need to be able to wrap the call to JobActivator.

I know that I can start a scope inside my triggered function but I would like to know if there are any point of extensibility in the sdk that allow us to do scoping ?

Thanks.

Thomas
  • 24,234
  • 6
  • 81
  • 125
  • Doesn't [this answer](http://stackoverflow.com/a/30454556/1370166) on the post you linked already answer your question? – TylerOhlsen Feb 03 '16 at 22:06
  • 1
    @TylerOhlsen, in fact not... I am able to create instance of my service each time my function is triggered but I am not able to find the proper scope so that I will be able to dispose resources... I will update my older answer. Thanks. – Thomas Feb 03 '16 at 22:11
  • When I create Azure WebJobs, I create them as standard console applications; I don't use the SDK at all. In a standard console application, the `Main` method is the [Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot). Problem solved. – Mark Seemann Feb 11 '16 at 09:08
  • 2
    @MarkSeemann, I would like to take advantage of the webjob sdk ^^ – Thomas Feb 11 '16 at 19:09
  • I need this to be answered as well... I was using InRequestScope() because I copied it from my MVC app, but then it definitely doesn't work when you start running a static function multiple times. I would need to have something like .InWebJobScope() – jsgoupil Apr 06 '16 at 01:08
  • @jsgoupil, which webjob trigger are you using ? – Thomas Apr 06 '16 at 01:15
  • @Thomas ServiceBusTrigger, QueueTrigger with occasionally using the host.Call() to test the static methods. So basically the JobActivator is a no go. – jsgoupil Apr 06 '16 at 01:17
  • @jsgoupil, for ServiceBusTrigger, you can create a custom `MessagingProvider` to handle scoping. see this answer: http://stackoverflow.com/a/33759649/4167200 – Thomas Apr 06 '16 at 01:20
  • Thanks @Thomas. That fixes half of my problem :) I was going to deprecate my service bus stuff for queues soon. anyway – jsgoupil Apr 06 '16 at 01:52
  • @jsgoupil, I've posted an answer for servicebustrigger and queuetrigger. – Thomas Apr 06 '16 at 23:21

1 Answers1

4

I've opened an request Add IDependencyScope to handle scoping to the Azure Webjob team.

I've create a small library to gather classes around Azure Webjobs and SimpleInjector :

For QueueTrigger and ServiceBustrigger, I've come accross these solutions :

  • ServiceBusTrigger (from this answer: https://stackoverflow.com/a/33759649/4167200):

    public sealed 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 ScopedMessageProcessor(_config.MessageOptions, _container);
        }
    
        private class ScopedMessageProcessor : MessageProcessor
        {
            private readonly Container _container;
    
            public ScopedMessageProcessor(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)
            {
                _container.GetCurrentExecutionContextScope()?.Dispose();
                return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
            }
        }
    }
    

    You can use your custom MessagingProvider in your JobHostConfiguration like

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

    public sealed class ScopedQueueProcessorFactory : IQueueProcessorFactory
    {
        private readonly Container _container;
    
        public ScopedQueueProcessorFactory(Container container)
        {
            _container = container;
        }
    
        public QueueProcessor Create(QueueProcessorFactoryContext context)
        {
            return new ScopedQueueProcessor(context, _container);
        }
    
        private class ScopedQueueProcessor : QueueProcessor
        {
            private readonly Container _container;
    
            public ScopedQueueProcessor(QueueProcessorFactoryContext context, Container container)
                : base(context)
            {
                _container = container;
            }
    
            public override Task<bool> BeginProcessingMessageAsync(CloudQueueMessage message, CancellationToken cancellationToken)
            {
                _container.BeginExecutionContextScope();
                return base.BeginProcessingMessageAsync(message, cancellationToken);
            }
    
            public override Task CompleteProcessingMessageAsync(CloudQueueMessage message, FunctionResult result,
                CancellationToken cancellationToken)
            {
                _container.GetCurrentExecutionContextScope()?.Dispose();
                return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
            }
        }
    }
    

    You can use your custom IQueueProcessorFactory in your JobHostConfiguration like this:

     var config = new JobHostConfiguration();
     config.Queues.QueueProcessorFactory = new ScopedQueueProcessorFactory(container);
    
Community
  • 1
  • 1
Thomas
  • 24,234
  • 6
  • 81
  • 125
  • have you actually tried this? From what I am looking your "Begin/Dispose" is going to work only if you are 100% sure the queue messages are not running on multiple threads in parallel?. Which I doubt that is the case. Unless you are telling me that the "BeginExecutionContextScope" should lock? then that doesn't sound good either. I'm trying to make this work with NInject with the .InScope() https://github.com/ninject/ninject/wiki/Object-Scopes – jsgoupil Apr 12 '16 at 01:42
  • @jsgoupil, the SimpleInjector "Per Execution Context Scope" is designed to deal with asynchronous flow: A scope is specific to the asynchronous flow. A method call on a different (unrelated) thread, will get its own scope. https://simpleinjector.readthedocs.org/en/latest/lifetimes.html#per-execution-context-scope-async-await – Thomas Apr 12 '16 at 03:30
  • One note about this method (for the QueueTrigger at least) is that when you manually trigger or replay functions from the webjob control pannel (example-app.scm.azurewebsites.net/azurejobs/#) then it doesn't seem to use the custom QueueProcessorFactory, so it causes an error. – Mingwei Samuel Aug 13 '16 at 09:57