Friday, November 30, 2018

Scoped DI Provider for Azure Function

The azure function does not provide an in built support for dependency injection (at least at the time of writing this article). There is however an user voice ticket marked as started at this point.

Currently, there is a good nuget package available using which you could bring DI support with little effort as mentioned in its repo home page. The author of this package Boris Wilhelms, also wrote a blog post mentioning the working of this package if you would like to understand how it works.

Anyways, I was however looking for even simplified workaround until Microsoft provides native support.

So, here is the approach that I followed.

Created following wrapper class for DI:

public static class FunctionScopedDIProvider
{
    private static IServiceProvider serviceProvider;
    private static readonly object locker = new object();

    public static async Task Using(Func<IServiceProvider, Task> action)
    {
        if (serviceProvider == null)
        {
            lock (locker)
            {
                if (serviceProvider == null)
                {
                    serviceProvider = BuildServiceProvider();
                }
            }
        }

        var scopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
        using (var scope = scopeFactory.CreateScope())
        {
            await action(scope.ServiceProvider);
        }
    }

    private static IServiceProvider BuildServiceProvider()
    {
        var services = new ServiceCollection();
        
        // TODO: Do your registrations here.
        // Ex: services.AddScoped<Imyservice, Myservice>();
        
        return services.BuildServiceProvider();
    }
}

Which can be used like this:

[FunctionName("Function1")]
public static async Task Run([QueueTrigger("myqueue-items", Connection = "")]string myQueueItem, ILogger log)
{
    await FunctionScopedDIProvider.Using(async provider =>
    {
        // The provider here would work as expeceted including scoped lifetime.
        // Ex: var myService = provider.GetService<IMyservice>();

        // TODO: You function logic goes here. 

        // A mock task completion as we don't have a real awaitable task here.
        await Task.CompletedTask;
    });

    log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
}

Note: The Using function of FunctionScopedDIProvider above not necessarily need to be async and so is the parameter. It can simply be an action delegate instead of function delegate.

This is how the non async version signature would look like.

public static void Using(Action<IServiceprovider> action)

Are you wondering why we need to do this, when we can easily get the DI provider by calling BuildServiceProvider method of ServiceCollectio? Well, its because singleton and scoped object liefetime support of the DI.

In other words, if you create new ServiceProvider each time, you won't get singleton behavior and if you reuse the ServiceProvider then you won't get scoped behavior (The reuse can be done by keeping it in a static variable).

So the solution that I come up with is by creating a static ServiceProvider  in a thread-safe manner and creating new scope each time with the help of in-built IServiceScopeFactory.

We can actually keep this stuff in the Run function's body itself but that's a cluttering code and not re-usable as well. So, created a nice wrapper with delegate using pattern (refer the second point in the article, titled - Measuring various function/method execution using StopWatch).

Enough talking, here is the full code. Enjoy your day!

Important: This code doesn't work if you would like to do the DI for function entry itself. For that you should use the solution provided by Boris Wilhelms which I mentioned in the beginning of this blog.

No comments:

Post a Comment