Access FunctionContext In Azure Functions Isolated Mode

Play this article

By looking at Title of article, It seems obvious that FunctionContext is available in Isolated Model in every function and so It is easy to pass that as input parameter to downstream function call.

Yes. This is fine but what if we want to make accessible as a Dependency Injection but not as a input parameter. This is what I am going to discuss over here.

Let's take example and we see what is the problem and what could be the solution for that.

image.png

In example, we have greeting service and it has a classlibrary that has request handler but this handler resides in separate library (It may possible that we have many library) and in log we need to write function invocationId.

  • For this we have FunctionContext in request.

  • Here is how request for Handler looks like.

public class GreetingRequest
        : IRequest<GreetingResponse>
    {
        public string Name { get; set; } = null!;
        public object FunctionContext { get; set; }
    }
  • Here is how function looks like.
 [Function("GreetingFunction")]
 public async Task<HttpResponseData> Greeting([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "greet/{name}")] HttpRequestData req,
            string name,
            FunctionContext functionContext)
        {
            this.logger.LogInformation("C# HTTP trigger function processed a request.");

            var greetingResponse = await this.mediator.Send(new GreetingRequest() {  Name = name , FunctionContext = functionContext });

            var response = req.CreateResponse(HttpStatusCode.OK);
            await response.WriteAsJsonAsync(greetingResponse);

            return response;
        }
  • Here is how handler look like.
public class GreetingRequestHandler
        : IRequestHandler<GreetingRequest, GreetingResponse>
    {
        private readonly ILogger<GreetingRequestHandler> logger;

        public GreetingRequestHandler(ILogger<GreetingRequestHandler> logger)
        {
            this.logger = logger;
        }
        public async Task<GreetingResponse> Handle(GreetingRequest request, CancellationToken cancellationToken)
        {            
            var invocationId = request.FunctionContext.GetType().GetProperty("InvocationId").GetValue(request.FunctionContext) as string;
            this.logger.LogInformation($"[GreetingRequestHandler] Called With {request.Name ?? string.Empty} and InvocationId {invocationId ?? String.Empty}");
            return new GreetingResponse() { Message = $"Hello {request.Name}" , InvocationId = invocationId };
        }
    }

Key problem with above approach is, It requires to pass FunctionContext with each request.

Is there any better way to do it ?

Solution

  • High Level It looks like following.

image.png

  • Create Interface that expose FunctionContext.
public interface IFunctionContextAccessor
{
        public FunctionContext? FunctionContext { get; set; }
}
  • Implementation of FunctionContextAccessor. This is almost inspire from HttpContextAccessor.
 internal sealed class DefaultFunctionContextAccessor
    : IFunctionContextAccessor
    {
        private static readonly AsyncLocal<FunctionContextHolder> _functionContextCurrent = new AsyncLocal<FunctionContextHolder>();

        public FunctionContext? FunctionContext
        {
            get
            {
                return _functionContextCurrent.Value?.Context;
            }
            set
            {
                var holder = _functionContextCurrent.Value;
                if (holder != null)
                {
                    holder.Context = null;
                }

                if (value != null)
                {
                    _functionContextCurrent.Value = new FunctionContextHolder { Context = value };
                }
            }
        }


        private class FunctionContextHolder
        {
            public FunctionContext? Context;
        }
    }

Note: In above if you look at line with AsyncLocal. This is important as it will give different context for each async boundary.

  • Following are some extension method.
public static class FunctionAccessorExtensions
    {
        public static IFunctionsWorkerApplicationBuilder UseFunctionContextAccessor(this IFunctionsWorkerApplicationBuilder functionsWorkerApplicationBuilder)
        {
            functionsWorkerApplicationBuilder.UseMiddleware<FunctionContextMiddleware>();
            return functionsWorkerApplicationBuilder;
        }

        public static IServiceCollection AddFunctionContextAccessor(this IServiceCollection services)
        {
            services.AddSingleton<IFunctionContextAccessor, DefaultFunctionContextAccessor>();
            return services;
        }
 }
  • In above line we register FunctionContextAccessor as Singleton. So Context accessor is singleton but due to AsyncLocal it will hold different value. Also if you try to make it Scope or Transient then it will work but when subscope created then it will not created. Such case happen when Custom Http MessageHandler is being used.

This explain very well by Andrew Lock in this article andrewlock.net/understanding-scopes-with-ih... That is in context with asp.net core webapi.

  • This is how it will be register with Function host.
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(wokrer =>
    {
        wokrer.UseFunctionContextAccessor();
    })
    .ConfigureServices(services =>
    {
        services.AddFunctionContextAccessor();
        services.AddMediatR(typeof(GreetingLibrary.GreetingRequest).Assembly);
    })
    .Build();

Now let's modify Request handler and see how it works.

  • Modified GreetingRequest
public class GreetingRequest
        : IRequest<GreetingResponse>
    {
        public string Name { get; set; } = null!;        
    }
  • Modified RequestHandler.
public class GreetingRequestHandler
        : IRequestHandler<GreetingRequest, GreetingResponse>
    {
        private readonly ILogger<GreetingRequestHandler> logger;
        private readonly IFunctionContextAccessor functionContextAccessor;

        public GreetingRequestHandler(ILogger<GreetingRequestHandler> logger,
            IFunctionContextAccessor functionContextAccessor)
        {
            this.logger = logger;
            this.functionContextAccessor = functionContextAccessor;
        }
        public async Task<GreetingResponse> Handle(GreetingRequest request, CancellationToken cancellationToken)
        {
            var invocationId = this.functionContextAccessor.FunctionContext!.InvocationId;
            this.logger.LogInformation($"[GreetingRequestHandler] Called With {request.Name ?? string.Empty} and InvocationId { invocationId ?? String.Empty}");
            return new GreetingResponse() { Message = $"Hello {request.Name}" , InvocationId = invocationId };
        }
    }

You see in above code is we now use IFunctionContextAccessor as well as FunctionContext get removed from Request. This makes request much cleaner and tied to business.

Download With FunctionContextAccessor

Summary

From my perspective, Solution gives clear separation of FunctionContext from Business Request. This will helpful in future if we have some business logic that needs to be call from Web API and Function App. It still required one more layer of abstraction but it works. I may put some update in that direction and probably create separate branch for that.

I have also suggested this at git with following issue id. This is not an issue but more on enhancement side. Also they will provide more insight about solution. github.com/Azure/azure-functions-dotnet-wor..