Error creating an IFunctionProvider with a non-static trigger
See original GitHub issueI have an IFunctionProvider
implementation for an Azure Function that creates a route for /healthcheck
. The entrypoint for my function metadata is a method called Somenamespace.TestTrigger.RenderHealthCheck
. My code looks like this:
public class TestTrigger
{
public static async Task<IActionResult> RenderHealthCheck(HttpRequest req)
{
return new OkObjectResult("OK");
}
}
public class HealthCheckTrigger : IFunctionProvider
{
public ImmutableDictionary<string, ImmutableArray<string>> FunctionErrors { get; }
public async Task<ImmutableArray<FunctionMetadata>> GetFunctionMetadataAsync()
{
var assembly = Assembly.GetExecutingAssembly();
var functionMetadata = new FunctionMetadata()
{
Name = nameof(TestTrigger.RenderHealthCheck),
FunctionDirectory = null,
ScriptFile = $"assembly:{assembly.FullName}",
EntryPoint = $"{typeof(TestTrigger).FullName}.{nameof(TestTrigger.RenderHealthCheck)}",
Language = "DotNetAssembly"
};
var jo = JObject.FromObject(new HttpBindingMetadata()
{
Methods = new List<string> { HttpMethods.Get },
Route = "HealthCheck",
AuthLevel = AuthorizationLevel.Anonymous,
});
var binding = BindingMetadata.Create(jo);
functionMetadata.Bindings.Add(binding);
var functionMetadataList = new List<FunctionMetadata>
{
functionMetadata
};
return await Task.FromResult(functionMetadataList.ToImmutableArray()).ConfigureAwait(false);
}
}
This works fine, and when I run the function and hit the /HealthCheck
endpoint I get my OK message. However, I’d like to make the RenderHealthCheck
method non-static, so I can use dependency injection in the constructor and access various services in my application. I changed my code to:
public class TestTrigger
{
private readonly HealthCheckService healthCheckService;
public TestTrigger(HealthCheckService healthCheckService)
{
this.healthCheckService = healthCheckService;
}
public async Task<IActionResult> RenderHealthCheck(HttpRequest req)
{
return new OkObjectResult("OK");
}
}
Now, when I hit the endpoint, I get the following exception:
Executed 'Functions.RenderHealthCheck' (Failed, Id=c02a9ba6-1ed3-4c07-8b38-214d345b6ff1, Duration=488ms)
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: Functions.RenderHealthCheck
---> System.NullReferenceException: Object reference not set to an instance of an object.
at lambda_method(Closure , TestTrigger , Object[] )
at Microsoft.Azure.WebJobs.Host.Executors.TaskMethodInvoker`2.InvokeAsync(TReflected instance, Object[] arguments) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\TaskMethodInvoker.cs:line 21
at Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker.MethodInvoker`2.InvokeAsync(Object target, Object[] parameters) in D:\a\1\s\src\WebJobs.Script\Description\DotNet\DotNetFunctionInvoker.cs:line 533
at Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker.InvokeCore(Object[] parameters, FunctionInvocationContext context) in D:\a\1\s\src\WebJobs.Script\Description\DotNet\DotNetFunctionInvoker.cs:line 272
at Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase.Invoke(Object[] parameters) in D:\a\1\s\src\WebJobs.Script\Description\FunctionInvokerBase.cs:line 82
at Microsoft.Azure.WebJobs.Script.Description.FunctionGenerator.Coerce[T](Task`1 src) in D:\a\1\s\src\WebJobs.Script\Description\FunctionGenerator.cs:line 225
at Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`2.InvokeAsync(Object instance, Object[] arguments) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionInvoker.cs:line 52
at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.InvokeWithTimeoutAsync(IFunctionInvoker invoker, ParameterHelper parameterHelper, CancellationTokenSource timeoutTokenSource, CancellationTokenSource functionCancellationTokenSource, Boolean thro
wOnTimeout, TimeSpan timerInterval, IFunctionInstance instance) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 555
at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstanceEx instance, ParameterHelper parameterHelper, ILogger logger, CancellationTokenSource functionCancellationTokenSource) in C:\projects\azure-webjobs-sdk-r
qm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 503
at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstanceEx instance, FunctionStartedMessage message, FunctionInstanceLogEntry instanceLogEntry, ParameterHelper parameterHelper, ILogger logger, CancellationToken
cancellationToken) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 281
--- End of inner exception stack trace --
I didn’t see anything in the FunctionMetadata
or HttpBindingMetadata
that seems to control if the entrypoint is static.
Repro steps
Run code above and verify it works if the method is static. Now try running as an instance method.
Expected behavior
Both static and instance methods should be supported for functions, just like those bound with a functions.json file.
Actual behavior
Only static methods are supported.
Known workarounds
One hacky workaround would be to make the constructor set a static property which could then be used by the static trigger method. I’d rather stay away from static properties for a variety of reasons.
Related information
Also posted this question on StackOverflow
- Package version
This is using Azure Functions v3 on .NET Core 3.1.
- Links to source
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:7 (1 by maintainers)
Top GitHub Comments
@eminencegrs Nope, didn’t solve it. My work around is to make the trigger static, and now I have to set
static
properties in the constructor so I can access DI services. Kinda hacky, but it works for my use-case. I agree this is a bug and should be fixed. The entireIFunctionProvider
mechanism seems unstable and undocumented, I think it’s a feature not really meant for production use yet.@MikeChristensen apologies for the delayed response here, but you’re correct in your last comment. This is something that was introduced with a very narrow scope to support internal requirements and not positioned as a publicly supported feature, so it hasn’t really seen a lot of investment.
I’ll leave this open to track, but at the moment, there are no plans to make changes to this functionality.