Hotchocolate: Azure Function DI Null Exception

Created on 18 Dec 2019  路  18Comments  路  Source: ChilliCream/hotchocolate

Describe the bug
Null Exception during Dependency Injection within Azure Functions. Working in 10.0.1 only.

To Reproduce
Using Repository located https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate

Steps to reproduce the behavior:

  1. Download the above sample repository
  2. Query data and get back results - Success
    eg
{
    droid(id:2000) {
        name
    }
}
  1. Update packages to any version after 10.0.1 (both 10.2.0 and 11.0.0-preview.70 have same issue)
  2. Run Query in step 2 - results in a NULL ref exception
{
    "data": {},
    "extensions": {},
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "code": null,
            "path": null,
            "locations": [],
            "exception": {
                "ClassName": "System.NullReferenceException",
                "Message": "Object reference not set to an instance of an object.",
                "Data": null,
                "InnerException": null,
                "HelpURL": null,
                "StackTraceString": "   at lambda_method(Closure , IResolverContext )\r\n   at DryIoc.Factory.<>c__DisplayClass26_0.<ApplyReuse>b__2() in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6596\r\n   at DryIoc.Scope.TryGetOrAdd(ImMap`1 items, Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7840\r\n   at DryIoc.Scope.GetOrAdd(Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7825\r\n   at DryIoc.Factory.ApplyReuse(Expression serviceExpr, Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6595\r\n   at DryIoc.Factory.GetExpressionOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6555\r\n   at DryIoc.Factory.GetDelegateOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6625\r\n   at DryIoc.Container.ResolveAndCacheDefaultFactoryDelegate(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 210\r\n   at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 195\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 101\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 79\r\n   at lambda_method(Closure , IServiceProvider , QueryDelegate )\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass0_0`1.<Create>b__1(IServiceProvider s, QueryDelegate n)\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass2_0`1.<CreateDelegate>b__0(IQueryContext context)\r\n   at HotChocolate.Execution.ExceptionMiddleware.InvokeAsync(IQueryContext context)",
                "RemoteStackTraceString": null,
                "RemoteStackIndex": 0,
                "ExceptionMethod": null,
                "HResult": -2147467261,
                "Source": "Anonymously Hosted DynamicMethods Assembly",
                "WatsonBuckets": null
            },
            "extensions": {
                "message": "Object reference not set to an instance of an object.",
                "stackTrace": "   at lambda_method(Closure , IResolverContext )\r\n   at DryIoc.Factory.<>c__DisplayClass26_0.<ApplyReuse>b__2() in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6596\r\n   at DryIoc.Scope.TryGetOrAdd(ImMap`1 items, Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7840\r\n   at DryIoc.Scope.GetOrAdd(Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7825\r\n   at DryIoc.Factory.ApplyReuse(Expression serviceExpr, Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6595\r\n   at DryIoc.Factory.GetExpressionOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6555\r\n   at DryIoc.Factory.GetDelegateOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6625\r\n   at DryIoc.Container.ResolveAndCacheDefaultFactoryDelegate(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 210\r\n   at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 195\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 101\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 79\r\n   at lambda_method(Closure , IServiceProvider , QueryDelegate )\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass0_0`1.<Create>b__1(IServiceProvider s, QueryDelegate n)\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass2_0`1.<CreateDelegate>b__0(IQueryContext context)\r\n   at HotChocolate.Execution.ExceptionMiddleware.InvokeAsync(IQueryContext context)"
            }
        }
    ],
    "contextData": {}
}

Expected behavior
I am not sure what fundamental changes occur in between 10.0.1 and 10.2.0, but something is affecting the DI stopping it from working in Azure Functions.

Desktop (please complete the following information):

  • OS: Windows 10
  • Visual Studio Community 2019

    • .Net Core 3.0

    • Azure Runtime v3

Additional context
Here are some of the variables I see when the exception is encountered.
image

My guess is there is a change in what this bit of code does which is causing the DI issues

 builder.Services.AddGraphQL(sp => SchemaBuilder.New()
                .AddServices(sp)
                .AddQueryType<QueryType>()
                .AddType<HumanType>()
                .Create());

Any pointers or assistance would be greatly apreciated

Thanks, Basil

馃攳 investigate

Most helpful comment

looks like manually registering the HashDocumentProvider after the GraphQL-initialization fixed the issue.

https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate/commit/a37c68a3384e3e47ebad7796318b0cad9b809338#diff-fbd03c53f6f1dc7701583e41f0cda5c8

the feat_nativeDI branch works now with the latest HC version:
https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate

All 18 comments

520 links to supplied example repo and suggests that there will be native support. @michaelstaib you mention there will be native support in version 11. How far off is this likely to be? and do you have any working examples that I could use?

@OneCyrus had similar issue, maybe he knows more

the problem is likely the scoped DI which has a different lifetime in AzF: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#service-lifetimes

we discussed this here: https://hotchocolategraphql.slack.com/archives/CD9TNKT8T/p1569335037212400

I dont have access to Slack. Could you provide a quick summary?

Is it an issue with how HC adds the GraphQL services during Configure?

image

i just gave this one a try again. and it doesn't look like the azure functions DI behaves incorrectly. the scoped instances are created correctly. there's also a sample on the AzF repo which shows this:

https://github.com/Azure/azure-functions-dotnet-extensions/tree/master/src/samples/DependencyInjection/Scopes
(though i had to update the dependencies to get it up and running)

it looks like there's really an issue where hotchocolate behaves strange in combination with AzF DI.

Hm... @michaelstaib could this be related to the custom service factory?

Somehow loosing state

I think the issue is how we build the execution builder. We will fix that in 11 since we will take apart the whole execution. But first I want to focus more on StrawberryShake and Visual Studio integration.

Once Rafi is done with the first public preview of bcp we can start work on this.

Ok. So @OneCyrus and I were debugging a bit.
The functions really behave strange. We could narrow down the issue and think it is related to the DI more specific to the DI injecting this class. https://github.com/ChilliCream/hotchocolate/blob/master/src/Core/Core/Execution/Middleware/ParseQueryMiddleware.cs

The DI containers fails when it tries to inject IDocumentHashProvider documentHashProvider

This behavior is easily reproducible by registering the IDocumentHashProvider in the function startup:

            builder.Services.AddSingleton<IDocumentHashProvider>((sp) => new MD5DocumentHashProvider());

and then fetch the dependency in function method. It doesn't matter if you do it with ctor injection, over the contextAccessor, or with the HTTPContext in the Request, it all fails :

            var services = req.HttpContext.RequestServices;
            var test = services .GetService<ReviewRepository>(); // works
            var test = services .GetService<IDocumentHashProvider>(); // 馃挘

And now it becomes really weird.

  1. We registered an instance. As we register a singleton i really expect this instance to be returned.
    In fact, the DI does try to activate the dependency via activator

  2. MD5DocumentHashProvider has two constructors. One without a parameter and one with a parameter. I don't know about the ctor scoring algorithm of this DI implementation. but it chooses the wrong one.
    image

  3. Well.. In the end we don't really need this instance anyway. If the DI injects null this case is handled in the ctor of ParseQueryMiddleware.cs. But the DI Container blows up as soon as it doesnt find a reference.

so GetService on their container is really GetRequiredService?

it looks like that. GetService stops the execution of the function when it doesn't find something and doesn't just return null. though it's not the implementation of the DI as it's the same Microsoft.Extension.DependencyInjection package which handles this.

though it's probably just because of using the wrong ctor for the HashProvider

hm.. it's not supposed to be created by the DI. The DI should just resolve it :D

looks like manually registering the HashDocumentProvider after the GraphQL-initialization fixed the issue.

https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate/commit/a37c68a3384e3e47ebad7796318b0cad9b809338#diff-fbd03c53f6f1dc7701583e41f0cda5c8

the feat_nativeDI branch works now with the latest HC version:
https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate

Thank you. I have been able to get it working using the suggested approach for now.

Keep me posted if it gets fixed in V 11.

Thanks

This one is now merged and will be part of 10.3.4

Was this page helpful?
0 / 5 - 0 ratings