Allow for calling custom code before and after function execution
See original GitHub issueI have been struggling to come up with a clean way to run code before and after a function runs, and I feel that it would be a worthwhile feature to add as it would promote modular, reusable code.
My specific use-case is to wrap my function invocation in a database transaction. Currently, I have to place a await UnitOfWork.SaveChangesAsync();
at the bottom of each function. I would like to streamline this by simply placing a attribute on my function.
A second use-case is to make it easier to implement benchmarking or telemetry such as Application Insights, as it would be very convenient to use DI to inject a telemetry tool, start a stop watch prior to function invocation, and then send it up after the fact.
Right now I can’t find a existing extensibility point for functions. The IJobActivator
interface lets me create the class, but has no knowledge of the function being executed.
Custom processors have before / after methods, but again do not have any knowledge of the actual function being executed. Additionally, the instances have already been disposed of at this point.
To solve this issue, I would propose adding a interface that defines before / after functionality. In my proof of concept, I built it out like this:
/// <summary>
/// Interface defining functionality for executing code before and after function invocation.
/// </summary>
public interface IExtendableFunction
{
/// <summary>
/// Executes before function invocation.
/// </summary>
/// <param name="info">The <see cref="MethodInfo"/> for the function invocation.</param>
/// <param name="arguments">The list of arguments passed to the function.</param>
/// <returns>A <see cref="Task"/> for the operation.</returns>
Task BeforeFunctionExecutionAsync(MethodInfo info, object[] arguments);
/// <summary>
/// Executes after function invocation.
/// </summary>
/// <param name="info">The <see cref="MethodInfo"/> for the function invocation.</param>
/// <param name="arguments">The list of arguments passed to the function.</param>
/// <returns>A <see cref="Task"/> for the operation.</returns>
Task AfterFunctionExecutionAsync(MethodInfo info, object[] arguments);
}
Then, a simple tweak to FunctionInvoker<TReflected>
:
- Adding a private readonly MethodInfo to the class and instanciating it from the constructor
- Updating InvokeAsync:
public async Task InvokeAsync(object[] arguments)
{
// Return a task immediately in case the method is not async.
await Task.Yield();
TReflected instance = _instanceFactory.Create();
using (instance as IDisposable)
{
if (instance is IExtendableFunction)
{
await((IExtendableFunction)instance).BeforeFunctionExecutionAsync(_methodInfo, arguments);
}
await _methodInvoker.InvokeAsync(instance, arguments);
if (instance is IExtendableFunction)
{
await((IExtendableFunction)instance).AfterFunctionExecutionAsync(_methodInfo, arguments);
}
}
}
If you are willing to add this functionality, I would be more than happy to submit a pull request with my changes.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:9 (4 by maintainers)
Top GitHub Comments
This would also make my life easier (though I may just not have a deep enough understanding of the WebJobs SDK yet). I’d like my functions invoked with DI with a scope of each invocation. I’d also like to to be able to add “job context information” to our Serilog infrastructure.
That works beautifully, and even flows nicely through inherited classes. 👍
Thank you so much. Excited to see it in a release in the near future.