'Creating an IFunctionProvider with a non-static trigger
I 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. Are instance methods simply not supported? Seems like a huge limitation if not. There's basically zero documentation of any sort on IFunctionProvider so not quite sure where to go from here. Any help would be appreciated!
Solution 1:[1]
If you really want to be able to inject HealthCheckService into your static function, it is possible to create a custom binding for it and resolve the service from the FunctionExecutionContext.
Based on https://microsoft.github.io/AzureTipsAndTricks/blog/tip247.html, I was able to do the following:
[assembly: WebJobsStartup(typeof(HealthBindingStartup))]
[Binding]
[AttributeUsage(AttributeTargets.Parameter)]
public class HealthAttribute : Attribute
{
}
[Extension(nameof(HealthBinding))]
public class HealthBinding : IExtensionConfigProvider
{
public void Initialize(ExtensionConfigContext context)
{
var rule = context.AddBindingRule<HealthAttribute>();
rule.BindToInput((HealthAttribute attr, ValueBindingContext context) =>
{
var service = context.FunctionContext.CreateObjectInstance<HealthCheckService>();
return Task.FromResult(service);
});
}
}
public static class HealthBindingExtension
{
public static IWebJobsBuilder AddHealthBinding(this IWebJobsBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddExtension<HealthBinding>();
return builder;
}
}
public class HealthBindingStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.AddHealthBinding();
}
}
With this, you can update your function and add [Health] HealthCheckService service and have this binding inject the service you want.
The metadata for this binding is:
{ "name": "service", "type": "health", "direction": "in" }
This is what you need when adding functions via the IFunctionProvider which ignores any attributes.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Thomas Bleijendaal |
