diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index c30b31a8f8ee..7c2c9bc4f3d9 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -32,7 +32,7 @@ public static partial class RequestDelegateFactory private static readonly MethodInfo ExecuteObjectReturnMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteObjectReturn), BindingFlags.NonPublic | BindingFlags.Static)!; private static readonly MethodInfo GetRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!; private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteResultWriteResponse), BindingFlags.NonPublic | BindingFlags.Static)!; - private static readonly MethodInfo StringResultWriteResponseAsyncMethod = GetMethodInfo>((response, text) => HttpResponseWritingExtensions.WriteAsync(response, text, default)); + private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteWriteStringResponseAsync), BindingFlags.NonPublic | BindingFlags.Static)!; private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = GetMethodInfo>((response, value) => HttpResponseJsonExtensions.WriteAsJsonAsync(response, value, default)); private static readonly MethodInfo LogParameterBindingFailureMethod = GetMethodInfo>((httpContext, parameterType, parameterName, sourceValue) => Log.ParameterBindingFailed(httpContext, parameterType, parameterName, sourceValue)); @@ -444,7 +444,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall, } else if (returnType == typeof(string)) { - return Expression.Call(StringResultWriteResponseAsyncMethod, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None)); + return Expression.Call(StringResultWriteResponseAsyncMethod, HttpContextExpr, methodCall); } else if (returnType.IsValueType) { @@ -704,6 +704,7 @@ private static async Task ExecuteObjectReturn(object? obj, HttpContext httpConte } else if (obj is string stringValue) { + SetPlaintextContentType(httpContext); await httpContext.Response.WriteAsync(stringValue); } else @@ -732,8 +733,10 @@ static async Task ExecuteAwaited(Task task, HttpContext httpContext) private static Task ExecuteTaskOfString(Task task, HttpContext httpContext) { - EnsureRequestTaskNotNull(task); + SetPlaintextContentType(httpContext); + EnsureRequestTaskNotNull(task); + static async Task ExecuteAwaited(Task task, HttpContext httpContext) { await httpContext.Response.WriteAsync(await task); @@ -747,6 +750,12 @@ static async Task ExecuteAwaited(Task task, HttpContext httpContext) return ExecuteAwaited(task!, httpContext); } + private static Task ExecuteWriteStringResponseAsync(HttpContext httpContext, string text) + { + SetPlaintextContentType(httpContext); + return httpContext.Response.WriteAsync(text); + } + private static Task ExecuteValueTask(ValueTask task) { static async Task ExecuteAwaited(ValueTask task) @@ -779,6 +788,8 @@ static async Task ExecuteAwaited(ValueTask task, HttpContext httpContext) private static Task ExecuteValueTaskOfString(ValueTask task, HttpContext httpContext) { + SetPlaintextContentType(httpContext); + static async Task ExecuteAwaited(ValueTask task, HttpContext httpContext) { await httpContext.Response.WriteAsync(await task); @@ -884,5 +895,10 @@ private static IResult EnsureRequestResultNotNull(IResult? result) return result; } + + private static void SetPlaintextContentType(HttpContext httpContext) + { + httpContext.Response.ContentType ??= "text/plain; charset=utf-8"; + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index 840319aed9c7..eaf0c96a8bfb 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -1174,7 +1174,7 @@ public static IEnumerable StringResult [Theory] [MemberData(nameof(StringResult))] - public async Task RequestDelegateWritesStringReturnValueAsJsonResponseBody(Delegate @delegate) + public async Task RequestDelegateWritesStringReturnValueAndSetContentTypeWhenNull(Delegate @delegate) { var httpContext = new DefaultHttpContext(); var responseBodyStream = new MemoryStream(); @@ -1187,6 +1187,21 @@ public async Task RequestDelegateWritesStringReturnValueAsJsonResponseBody(Deleg var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray()); Assert.Equal("String Test", responseBody); + Assert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); + } + + [Theory] + [MemberData(nameof(StringResult))] + public async Task RequestDelegateWritesStringReturnDoNotChangeContentType(Delegate @delegate) + { + var httpContext = new DefaultHttpContext(); + httpContext.Response.ContentType = "application/json; charset=utf-8"; + + var requestDelegate = RequestDelegateFactory.Create(@delegate); + + await requestDelegate(httpContext); + + Assert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType); } public static IEnumerable IntResult