Azure 函数中间件:如何返回自定义 HTTP 响应?

Kzr*_*tof 13 c# azure azure-functions asp.net-core-middleware .net-5

我正在探索运行在 Azure Function 上的内容.net 5,并发现了新的中间件功能

我构建了一个像这样的虚拟中间件:

public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
    private readonly ILogger<ExceptionLoggingMiddleware> m_logger;

    public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
    {
        m_logger = logger;
    }

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        try
        {
            await next(context);
        }
        catch (Exception unhandledException)
        {
            m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的用例中,Azure 函数是一个 HTTP 触发函数:

public sealed class StorageAccountsFunction
{
    private readonly ILogger<StorageAccountsFunction> m_logger;

    public StorageAccountsFunction
    (
        ILogger<StorageAccountsFunction> logger
    )
    {
        m_logger = logger;
    }

    [Function("v1-post-storage-account")]
    public async Task<HttpResponseData> CreateAsync
    (
        [HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")] 
        HttpRequestData httpRequestData, 
        FunctionContext context
    )
    {
        m_logger.LogInformation("Processing a request to create a new storage account");

        throw new Exception("Oh no! Oh well..");
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的进程内运行的函数应用程序中.net core 3.1,每个函数都有责任捕获未处理的异常(通过基类)并返回适当的 HTTP 状态代码。

我希望将该逻辑放在中间件中,而不是将其集中化并避免将来出现任何错误。

问题

异常被中间件正确捕获。但是,我不知道如何更改响应并返回更合适的内容,而不是500 Internal Server Error我现在得到的内容?

Sil*_*Sil 16

从Microsoft.Azure.Functions.Worker版本 1.8.0 开始,现在已原生支持此功能。

引入了FunctionContextHttpRequestExtensions类现在您可以

using Microsoft.Azure.Functions.Worker;


public class MyMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        // To access the RequestData
        var req = await context.GetHttpRequestDataAsync();

        // To set the ResponseData
        var res = req!.CreateResponse();
        await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
        context.GetInvocationResult().Value = res;
    }
}

Run Code Online (Sandbox Code Playgroud)

  • 有关工作示例,请参阅 Microsoft 的异常处理中间件示例:https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs (2认同)

小智 14

根据这个问题,目前还没有关于此问题的官方实现,但他们也提到了一个“hacky解决方法”,直到将正确的功能直接实现到Azure函数中

我们为 FunctionContext 创建了一个扩展方法:

internal static class FunctionUtilities
{
    internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
    {
        var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
        var functionBindingsFeature = keyValuePair.Value;
        var type = functionBindingsFeature.GetType();
        var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
        return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
    }

    internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
    {
        var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
        var functionBindingsFeature = keyValuePair.Value;
        var type = functionBindingsFeature.GetType();
        var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
        result.SetValue(functionBindingsFeature, response);
    }
}
Run Code Online (Sandbox Code Playgroud)

中间件中的用法如下:

public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
   try
   {
       await next(context);
   }
   catch (Exception ex)
   {
       if (ex.InnerException is *NameOfExceptionYouNeed* e)
       {
           var req = context.GetHttpRequestData();
           var res = await req.ErrorResponseAsync(e.Message);
           context.InvokeResult(res);
           return;
       }

       throw;
   }
}
Run Code Online (Sandbox Code Playgroud)