0

I have an Azure Durable Function that worked before I added custom middleware. Do Azure Functions work with custom middleware?

I've added the middleware to manage custom authentication and to standardise the response and work around a built-in issue that executes and locks the response when using the built-in req.CreateResponse().WriteAsJsonAsync(someModel) reply.

Having another public function and then this Durable Function would mean my costs go up. Any thoughts or suggestions would be appreciated.

I should state the function works, but it never hits the orchestration with the middleware.

EDIT:

It turns out you can't access the HttpRequestData in the middleware if you're using the Orchestration Method or any Action Methods in the middleware.

So I set the methods anes in the Environmental settings seperated by a semi-colon (;) and used this method:

private bool IsDurableFunction(FunctionContext context)     
{
    var durabableMethodStr = Environment.GetEnvironmentVariable("DurableMethods") ?? "";

    if(String.IsNullOrWhiteSpace(durabableMethodStr)) return false;

    var durabableMethodArry = durabableMethodStr.Split(";");

    if(!durabableMethodArry.Contains(context.FunctionDefinition.Name)) 
        return false;

    return true;
}

In the middleware I then check to make sure the HttpRequestData is never accessed and it works.

1
  • Did you register custom middleware in program.cs? builder.UseMiddleware<CustomMiddleware>(); Commented Apr 25 at 10:20

1 Answer 1

0

I have implemented custom middleware with Durable Azure functions. You can also refer my SO answer.

Steps followed:

Create a Custom Middleware class:

internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var logger = context.GetLogger("StampHttpHeaderMiddleware");

        var request = await context.GetHttpRequestDataAsync();

        if (request != null && request.Headers != null &&
            request.Headers.TryGetValues("x-correlationId", out var values))
        {
            var correlationId = values.FirstOrDefault();
            logger.LogInformation("Correlation ID received: {correlationId}", correlationId);
        }
        else
        {
            logger.LogDebug("Skipping correlationId check - not an HTTP-triggered function.");
        }

        await next(context);
    }
}

Register custom middleware with worker in Program.cs:

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();
builder.UseMiddleware<StampHttpHeaderMiddleware>();
builder.Build().Run();

Function.cs:

[Function(nameof(Function1))]
public static async Task<List<string>> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(Function1));
    logger.LogInformation("Saying hello.");
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "London"));

    return outputs;
}

[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("SayHello");
    logger.LogInformation("Saying hello to {name}.", name);
    return $"Hello {name}!";
}

[Function("Function1_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
   [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
   [DurableClient] DurableTaskClient client,
   FunctionContext executionContext)
{
    var logger = executionContext.GetLogger("Function1_HttpStart");
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(Function1));

    logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

Output:

enter image description here

enter image description here

Functions:

        Function1_HttpStart: [GET,POST] http://localhost:7076/api/Function1_HttpStart

        Function1: orchestrationTrigger

        SayHello: activityTrigger

For detailed output, run func with --verbose flag.
[2025-04-25T10:17:26.985Z] Executing 'Functions.Function1_HttpStart' (Reason='This function was programmatically called via the host APIs.', Id=13fb0eb3-6a05-4dc0-b934-a8a72ad63c15)
[2025-04-25T10:17:27.595Z] Correlation ID received: 12345
[2025-04-25T10:17:27.684Z] Scheduling new Function1 orchestration with instance ID 'd6b1fc255d4a498c9db759018cf6e683' and 0 bytes of input data.
[2025-04-25T10:17:27.930Z] Started orchestration with ID = 'd6b1fc255d4a498c9db759018cf6e683'.
[2025-04-25T10:17:28.036Z] Executed 'Functions.Function1_HttpStart' (Succeeded, Id=13fb0eb3-6a05-4dc0-b934-a8a72ad63c15, Duration=1084ms)
[2025-04-25T10:17:28.127Z] Executing 'Functions.Function1' (Reason='(null)', Id=44a41f0a-fbd2-44a0-8ad1-21469fe8e2a4)
[2025-04-25T10:17:28.134Z] Host lock lease acquired by instance ID '000000000000000000000000F72731CC'.
[2025-04-25T10:17:28.302Z] Saying hello.
[2025-04-25T10:17:28.359Z] Executed 'Functions.Function1' (Succeeded, Id=44a41f0a-fbd2-44a0-8ad1-21469fe8e2a4, Duration=292ms)
[2025-04-25T10:17:28.447Z] Executing 'Functions.SayHello' (Reason='(null)', Id=5f2917c7-9c75-4c5f-9318-08de480aa1f0)
[2025-04-25T10:17:28.463Z] Saying hello to Tokyo.
[2025-04-25T10:17:28.466Z] Executed 'Functions.SayHello' (Succeeded, Id=5f2917c7-9c75-4c5f-9318-08de480aa1f0, Duration=23ms)
[2025-04-25T10:17:28.543Z] Executing 'Functions.Function1' (Reason='(null)', Id=5363b179-36e1-4445-8141-e68412baea3e)
[2025-04-25T10:17:28.562Z] Executed 'Functions.Function1' (Succeeded, Id=5363b179-36e1-4445-8141-e68412baea3e, Duration=21ms)
[2025-04-25T10:17:28.614Z] Executing 'Functions.SayHello' (Reason='(null)', Id=b23fb88c-5229-483a-99f2-3014dfec33c2)
[2025-04-25T10:17:28.623Z] Saying hello to Seattle.
[2025-04-25T10:17:28.626Z] Executed 'Functions.SayHello' (Succeeded, Id=b23fb88c-5229-483a-99f2-3014dfec33c2, Duration=11ms)
[2025-04-25T10:17:28.666Z] Executing 'Functions.Function1' (Reason='(null)', Id=a34e9c1f-59fc-46da-964e-7264d0abde5f)
[2025-04-25T10:17:28.673Z] Executed 'Functions.Function1' (Succeeded, Id=a34e9c1f-59fc-46da-964e-7264d0abde5f, Duration=8ms)
[2025-04-25T10:17:28.717Z] Executing 'Functions.SayHello' (Reason='(null)', Id=3d7b7445-9881-4f33-b91e-8006ed704353)
[2025-04-25T10:17:28.725Z] Saying hello to London.
[2025-04-25T10:17:28.727Z] Executed 'Functions.SayHello' (Succeeded, Id=3d7b7445-9881-4f33-b91e-8006ed704353, Duration=10ms)
[2025-04-25T10:17:28.769Z] Executing 'Functions.Function1' (Reason='(null)', Id=2cc76746-dd08-4326-86b9-c41cc0c68210)
[2025-04-25T10:17:28.786Z] Executed 'Functions.Function1' (Succeeded, Id=2cc76746-dd08-4326-86b9-c41cc0c68210, Duration=17ms)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.