4

Is there a way to hook into the WebJobs function execution so we can have a scope per function? Something like this:

kernel.Bind<MyDbContext>().ToSelf().InWebJobFunctionScope();

I would like to use the InScope() from Ninject, but I don't know where I can find something similar to the static HttpContext.Current but for WebJob currently running.

jsgoupil
  • 3,788
  • 3
  • 38
  • 53
  • Hi JS, I've opened an issue https://github.com/Azure/azure-webjobs-sdk-extensions/issues/39 but can you use the InThreadScope ? – Thomas Apr 12 '16 at 21:15
  • @Thomas I would have thought that thread scope will start behaving incorrectly when using async/await pattern. I tried to use BeginBlock for each static function and everything started to fail. So I am now using InNamedScope with a CreateNameScope with the preserve context nuget package to finally get something working for my db context... Definitely painful and I doubt name scope was supposed to work like that. – jsgoupil Apr 12 '16 at 21:18

1 Answers1

8

I know that this is an old one, but I had the same dramas. Since a more recent version of web jobs you can use instances and instance methods, and pass in custom IJobActivator instances. It's surprisingly easy.

It works perfectly with Ninject. I haven't seen any Ninject examples, so ...

public class MyJobActivator : IJobActivator
{
    protected readonly IKernel _kernel;

    public MyJobActivator(IKernel kernel)
    {
        _kernel = kernel;
    }

    public T CreateInstance<T>()
    {
        return _kernel.Get<T>();
    }
}


public class MyBindings : NinjectModule
{
    public override void Load()
    {     
        Bind(typeof(DbContext)).To(typeof(MyEntities));
    }
}

class Program
{        
    static void Main()
    {
        using (IKernel kernel = new StandardKernel(new MyBindings()))
        {
            var jobHostConfiguration = new JobHostConfiguration
            {
                JobActivator = new MyJobActivator(kernel)
            };

            var host = new JobHost(jobHostConfiguration);

            // The following code will invoke a function called ManualTrigger and 
            // pass in data (value in this case) to the function
            host.Call(typeof(Reminders).GetMethod("ManualTrigger"), new { value = 20 });
        }
    }
}


public class Reminders
{
    private readonly IMyService _myService;

    public Reminders(IMyService myService)
    {
        _myService = myService;
    }

    // This function will be triggered based on the schedule you have set for this WebJob
    // This function will enqueue a message on an Azure Queue called queue
    [NoAutomaticTrigger]
    public async Task ManualTrigger(TextWriter log, int value, TextWriter logger)
    {
        try
        {   
            // process the notification request
            await _myService.FindAndSendReminders();
            await _myService.SaveChangesAsync();
        }
        catch (Exception e)
        {
            logger.WriteLine(e.Message);
            Console.WriteLine(e.Message);
            throw;
        }
    }
}

EDIT: In addition to the above I've seen recently learned that you may not need to use the host.Call(typeof(Reminders).GetMethod("ManualTrigger"), at least for continuous web jobs.

You simply make your Functions class non static and add a constructor for injection, and then make your processing method non static. This is Illustrated below.

public class Program
{        
    static void Main()
    {
        using (IKernel kernel = new StandardKernel(new MyBindings()))
        {
            var jobHostConfiguration = new JobHostConfiguration
            {
                JobActivator = new MyJobActivator(kernel)
            };

            var host = new JobHost(jobHostConfiguration);

            // The following code ensures that the WebJob will be running continuously
            host.RunAndBlock();
        }
    }
}


public class Functions
{
    private readonly IMyService _myService;

    public Functions(IMyService myService)
    {
        _myService = myService;
    }        

    public async Task ProcessReminders([QueueTrigger("reminder-requests")] string notificationMessage, TextWriter logger)
    {
        try
        {   
            // process the notification request
            await _myService.FindAndSendReminders();
            await _myService.SaveChangesAsync();
        }
        catch (Exception e)
        {
            logger.WriteLine(e.Message);
            Console.WriteLine(e.Message);
            throw;
        }
    }
}

I adapted my original code from an article I found for Autofac

http://www.jerriepelser.com/blog/dedependency-injection-with-autofac-and-webjobs

See also

Dependency injection using Azure WebJobs SDK?

And for continuous webjobs

http://www.ryansouthgate.com/2016/05/10/azure-webjobs-and-dependency-injection/

Community
  • 1
  • 1
Troyka
  • 101
  • 1
  • 3
  • For some reasons, I had reason to believe this was not working with concurrency, and/or was not working when calling with the GetMethod() trick. @Thomas, do you think this covers what we were looking for long time ago :P ? – jsgoupil Jul 27 '16 at 14:04
  • jsgoupil I've updated my answer for continuous webjobs that doesn't require the use of getmethod. Hopefully it helps. – Troyka Aug 02 '16 at 22:40
  • @Troyka What's your opinion for lifecycle of Entity Framework DbContext objects within triggered webjobs? Obviously the webjob process is constantly alive which says maybe I should scope the DbContext to a singleton, but on the other hand the functions are involved per-message so I could scope the DbContext per function execution. What would you recommend? – Howiecamp Jun 23 '17 at 15:56