Is it possible for Function Middlewares and Hot Chocolate Middlewares to be used together when using Azure Functions in an isolated-process?
For example:
Consider Authentication middleware that is defined at a Function level via IFunctionWorkerMiddleware
. When the function is triggered, the Authentication Middleware is triggered and if authentication succeeds, then passes the control to HotChocolate Middleware for Authorization checks, validation checks etc.
both the middleware work in isolation, but when i use them in conjunction, then only the Function Middleware is trigger (as it is the first middleware in the pipeline) but the control is never handed over to HotChocolate middlewares.
I am using HotChocolate v13
, with .Net 6
and Function Version v4
In Short: Yes it is possible to use Azure Function Middleware and Hot Chocolate Middleware to be used together, when working with isolated process Azure Functions.
i decided to write an article about it on Medium: https://medium.com/@vivek.vardhan86/sharing-states-between-function-middleware-and-hot-chocolate-middleware-in-isolated-process-azure-6ea32b752eed
but if someone cannot read it, then here is the full article:
Step 1: Define the SharedContext
The Shared Context serves as a shared storage medium between different middleware components.
public class SharedContext
{
public string SharedData { get; set; }
}
Step 2: Create an Azure Function Middleware
Create a middleware class that implements IFunctionsWorkerMiddleware. This middleware sets a value in the Shared Context.
public class CustomFunctionMiddleware : IFunctionsWorkerMiddleware
{
public CustomFunctionMiddleware() { }
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
var sharedContext = context.InstanceServices.GetService<SharedContext>()!;
sharedContext.SharedData = "Some Data";
context.Items["shared_context"] = sharedContext;
await next(context);
}
}
Since SharedContext will be registered as a scoped service, it should not be injected via the Constructor, since it will be treated as singleton when using isolated-process Azure Functions.
Step 3: Create a Custom HttpRequestInterceptor in HotChocolate
This interceptor will be triggered after all the Azure Function Middleware in the pipeline are executed but before the GraphQL middleware and resolvers are invoked .
Extract the SharedContext from the HttpContext available in DefaultHttpRequestInterceptor or the IHttpRequestInterceptor interface available in Hot Chocolate. Now inject the SharedContext into Hot Chocolate’s GraphQL request pipeline as a Global State.
public class CustomHttpRequestInterceptor : DefaultHttpRequestInterceptor
{
public CustomHttpRequestInterceptor() { }
public override ValueTask OnCreateAsync(HttpContext httpContext, IRequestExecutor requestExecutor,
IQueryRequestBuilder requestBuilder, CancellationToken cancellationToken)
{
var sharedContext = httpContext.Items["shared_context"] as SharedContext;
requestBuilder.AddGlobalState("sharedContext", sharedContext);
return base.OnCreateAsync(httpContext.Items, requestExecutor, requestBuilder, cancellationToken);
}
}
Step 4: Implement GraphQL Middleware
Now that the Shared Context is added as a Global State it is available to all GraphQL middleware either via Middleware Context or Resolver Context
public class GraphQLMiddleware
{
private readonly FieldDelegate next;
public GraphQLMiddleware(FieldDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(IMiddlewareContext context)
{
var sharedContext = context.GetGlobalStateOrDefault<SharedContext>("sharedContext");
// Your logic here
await next(context);
}
}
Step 5: Register Components
Register your components in your Program.cs. This is critical for the dependency injection framework to know which implementations to use.
Make sure that you register the Shared Context as scoped so that each time a Http Trigger is activated via a function call, your SharedContext is scoped to that call. This way every new GraphQL Query or Mutation call will get a new copy of Shared Context and there will be no data bleeding between different GraphQL requests.
Step 5.1: GraphQL Middleware Registration
public class QueryType : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor
.Field("example")
.Use<GraphQLMiddleware>()
.Resolve(context =>
{
// Omitted for brevity
});
}
}
Step 5.2: isolated-process Azure Function Host Builder
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder.Services
.AddGraphQLFunction()
.AddHttpRequestInterceptor<CustomHttpRequestInterceptor>()
.AddQueryType<QueryType>();
builder.Services.AddScoped<SharedContext>();
builder.UseMiddleware<CustomFunctionMiddleware>();
})
.Build();
host.Run();