devlord
devlord

Reputation: 4164

AzureFunctions.Autofac threadsafe dependency injection issue

I am using AzureFunctions.Autofac to inject into my Azure Functions web api. An example of the config:

  public class DIConfig
    {
        public DIConfig()
        {
            DependencyInjection.Initialize(builder =>
            {
                // DAL
                builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerLifetimeScope();
                builder.RegisterType<SecretCompanyContext>().InstancePerLifetimeScope();
                builder.RegisterType<SecretCompanyContext>().As<ICartContext>().InstancePerLifetimeScope();
                builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();

                // Services               
                builder.RegisterType<InventoryServices>().As<IInventoryServices>().InstancePerLifetimeScope();

                // Controllers ported from ASP.NET MVC Web API
                builder.RegisterType<InventoryController>().InstancePerLifetimeScope();
            });
        }

Then my Azure functions, I have one class that defines all methods in the API

    [DependencyInjectionConfig(typeof(DIConfig))]
    public class InventoryFunctions : FunctionsApi
    {
        [FunctionName("GetProductsByCategory")]
        // /inventory/categories/{id}/products
        public static async Task<HttpResponseMessage> GetProductsByCategory(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory/categories/{id}/products")]
            HttpRequestMessage req,
            TraceWriter log,
            int id,
            [Inject] InventoryController controller)
        {
            // do stuff
            var result = await controller.GetProductsByCategory(id);
            return JsonResponse(result, HttpStatusCode.OK);
        }

        [FunctionName("GetInventoryBySku")]
        // /inventory/skus?sku=ASDF&sku=ASDG&sku=ASDH
        public static async Task<HttpResponseMessage> GetInventoryBySku(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory")]
            HttpRequestMessage req,
            TraceWriter log,
            [Inject] InventoryController controller)
        {
            // do stuff
            var result = await controller.QueryInventoryBySkuList(skuList);
            return JsonResponse(result, HttpStatusCode.OK);
        }

        [FunctionName("UpdateProductsQuantity")]
        // /inventory
        // Post
        public static async Task<HttpResponseMessage> UpdateProductsQuantity(
            [HttpTrigger(AuthorizationLevel.Function, "put", Route = "inventory")]
            HttpRequestMessage req,
            TraceWriter log,
            [Inject] InventoryController controller)
        {
            // do stuff
            var inventoryProducts = await req.Content.ReadAsAsync<List<InvProductOperation>>();
            var result = await controller.UpdateAvailableProductsQuantity(inventoryProducts);
            return JsonResponse(result, HttpStatusCode.OK);
        }

But I keep getting this error:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

I have verified that async and await are used properly, so following the error message's recommendation isn't fixing it. What appears to be the issue is that IDbContext is not honoring the InstancePerLifetimeScope as expected. Is this happening because I have more than one method in my InventoryFunctions class? Or is AzureFunctions.Autofac not threadsafe?

Upvotes: 0

Views: 597

Answers (2)

devlord
devlord

Reputation: 4164

I was going by this SO answer: Autofac - InstancePerHttpRequest vs InstancePerLifetimeScope which said that InstancePerLifetimeScope was the non-ASP.NET equivalent of InstancePerRequest.

I spoke to the developers and they said the truth is that getting one DbContext per HttpRequest was the default behavior when you simply register using builder.RegisterType<SecretCompanyContext>.As<IDbContext>() so there's some misinformation out there.

So the solution is, instead of using

builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();

or

builder.RegisterType<SecretCompanyContext>().As<IDbContext>().InstancePerLifetimeScope();

one should just use

builder.RegisterType<SecretCompanyContext>().As<IDbContext>();

if the goal is one instance per HTTP request.

Upvotes: 1

alsami
alsami

Reputation: 9835

Change the registration of the DbContext to this:

 builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();

You can find a deeper explanation of mine for why this is happening here.

Upvotes: 1

Related Questions