diff --git a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md index 97bb2a3ba..b837fa992 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md +++ b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md @@ -6,7 +6,7 @@ ### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore -- +- Fix intermittent error `IFeatureCollection has been disposed` exception in multiple-output binding scenarios. (#2896) ### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs index 83dbaeac0..211d6c5bc 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs @@ -9,8 +9,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Routing; -using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters; using Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Infrastructure; +using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.DependencyInjection; @@ -85,6 +85,9 @@ private static async Task TryHandleHttpResult(object? result, FunctionCont // processing is required. context.GetInvocationResult().Value = null; break; + case AspNetCoreHttpResponseData when !isInvocationResult: + await TryClearHttpOutputBinding(context); + break; case IResult iResult: await iResult.ExecuteAsync(httpContext); break; @@ -105,6 +108,21 @@ private static Task TryHandleOutputBindingsHttpResult(FunctionContext cont : TryHandleHttpResult(httpOutputBinding.Value, context, httpContext); } + private static Task TryClearHttpOutputBinding(FunctionContext context) + { + var httpOutputBinding = context.GetOutputBindings() + .FirstOrDefault(a => string.Equals(a.BindingType, HttpBindingType, StringComparison.OrdinalIgnoreCase)); + + if (httpOutputBinding is null) + { + return Task.FromResult(false); + } + + httpOutputBinding.Value = null; + + return Task.FromResult(true); + } + private static void AddHttpContextToFunctionContext(FunctionContext funcContext, HttpContext httpContext) { funcContext.Items.Add(Constants.HttpContextKey, httpContext); diff --git a/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs b/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs index 9c10358ed..ae5fc346d 100644 --- a/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs +++ b/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs @@ -170,6 +170,24 @@ public async Task InvocationResultNull_WhenResultIsTypeAspNetCoreHttpResponseDat test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); } + [Fact] + public async Task HttpResultOutputBindingNull_WhenUsingAspNetCoreHttpResponseDataInMultiOutputBinding() + { + var test = SetupTest("httpTrigger"); + var mockDelegate = new Mock(); + + SetUpAspNetCoreHttpResponseDataBindingInfo(test.FunctionContext, false); + + var funcMiddleware = new FunctionsHttpProxyingMiddleware(test.MockCoordinator.Object); + await funcMiddleware.Invoke(test.FunctionContext, mockDelegate.Object); + + var httpOutputBinding = test.FunctionContext.GetOutputBindings() + .FirstOrDefault(a => string.Equals(a.BindingType, "http", StringComparison.OrdinalIgnoreCase)); + + Assert.Null(httpOutputBinding.Value); + test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); + } + private static (FunctionContext FunctionContext, HttpContext HttpContext, Mock MockCoordinator) SetupTest(string triggerType, IDictionary outputBindings = null) { var inputBindings = new Dictionary()