Skip to content

Function Filters

Fabio Cavalcante edited this page May 16, 2023 · 14 revisions

🚧 This features is still in preview. Please be cautious using this in production applications. 🚧

Overview

Function Filters provide a way to customize the WebJobs execution pipeline with your own logic. Filters are very similar in to ASP.NET Filters. They can be implemented as declarative attributes that can be applied to your job functions/classes.

Filters allow you to encapsulate common logic to be shared across many different functions. They also allow you to centralize logic for cross cutting concerns (e.g. validation, logging, error handling, etc.).

Here's an example that shows both a method level Invocation Filter as well as an Exception Filter specified at the class level:

[ErrorHandler]
public static class Functions
{
    public static void BlobTrigger(
        [BlobTrigger("test")] string blob)
    {
        Console.WriteLine("Processed blob: " + blob);
    }

    [WorkItemValidator]
    public static void ProcessWorkItem(
        [QueueTrigger("test")] WorkItem workItem)
    {
        Console.WriteLine($"Processed work item {workItem.ID}");
    }
}

And here are the filter definitions:

public class WorkItemValidatorAttribute : FunctionInvocationFilterAttribute
{
    public override Task OnExecutingAsync(
        FunctionExecutingContext executingContext, CancellationToken cancellationToken)
    {
        executingContext.Logger.LogInformation("WorkItemValidator executing...");

        var workItem = executingContext.Arguments.First().Value as WorkItem;
        string errorMessage = null;
        if (!TryValidateWorkItem(workItem, out errorMessage))
        {
            executingContext.Logger.LogError(errorMessage);
            throw new ValidationException(errorMessage);
        }
                
        return base.OnExecutingAsync(executingContext, cancellationToken);
    }

    private static bool TryValidateWorkItem(WorkItem workItem, out string errorMessage)
    {
        errorMessage = null;

        if (string.IsNullOrEmpty(workItem.ID))
        {
            errorMessage = "ID cannot be null or empty.";
            return false;
        }
        if (workItem.Priority > 100 || workItem.Priority < 0)
        {
            errorMessage = "Priority must be between 0 and 100";
            return false;
        }

        return true;
    }
}

public class ErrorHandlerAttribute : FunctionExceptionFilterAttribute
{
    public override Task OnExceptionAsync(
        FunctionExceptionContext exceptionContext, CancellationToken cancellationToken)
    {
        // custom error handling logic could be written here
        // (e.g. write a queue message, send a notification, etc.)

        exceptionContext.Logger.LogError(
            $"ErrorHandler called. Function '{exceptionContext.FunctionName}"+
            ":{exceptionContext.FunctionInstanceId} failed.");

        return Task.CompletedTask;
    }
}

Filter Types

Currently we support the following filter types:

  • Invocation Filters - have both Executing/Executed methods that are called immediately before and immediately after the target job function is invoked.
  • Exception Filters - invoked whenever the target job function fails execution for any reason. Exception filters will be called for exceptions occurring at any stage in the execution pipeline, including input binding resolution, exceptions occurring in the target function (user code), output binding flush, etc.

In the future we might expand the set of filter types we support to allow customization of other stages of the execution pipeline.

Filter attributes can be applied at the class or method level. When applied at the class level, the filter will be run for all job functions in the class. Filters can also be applied globally by adding filters to the JobHostConfiguration service collection, e.g.:

var extensions = config.GetService<IExtensionRegistry>();
extensions.RegisterExtension<IFunctionInvocationFilter>(myFilter);

A job class can also implement a filter interface directly. To do so the class and function methods cannot be static. For example, here is a class implementing both the Invocation and Exception filter interfaces:

public class Functions : IFunctionExceptionFilter, IFunctionInvocationFilter
{
    public void ProcessWorkItem(
        [QueueTrigger("test")] WorkItem workItem)
    {
        Console.WriteLine($"Processed work item {workItem.ID}");
    }

    public Task OnExceptionAsync(FunctionExceptionContext exceptionContext, CancellationToken cancellationToken)
    {
        // TODO: your logic here
        return Task.CompletedTask;
    }

    public Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
    {
        // TODO: your logic here
        return Task.CompletedTask;
    }

    public Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)
    {
        // TODO: your logic here
        return Task.CompletedTask;
    }
}

Behavior

The execution order for filters is determined by the scope at which each filter is declared. The scopes (in order) are: Instance, Global, Class, Method.

The execution model is "Russian Doll" - Instance filters surround Global filters which surround Class filters which surround Method filters. As a result of this nesting, for multiple filters with both "executing"/"executed" methods (like invocation filters) the "executing" portion of the filters runs in the reverse order.

Clone this wiki locally