diff --git a/src/Mvc/Mvc.Core/src/ContentResult.cs b/src/Mvc/Mvc.Core/src/ContentResult.cs
index e1d86fc1f03f..060aa72c40bf 100644
--- a/src/Mvc/Mvc.Core/src/ContentResult.cs
+++ b/src/Mvc/Mvc.Core/src/ContentResult.cs
@@ -3,16 +3,22 @@
using System;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc
{
///
/// A that when executed will produce a response with content.
///
- public class ContentResult : ActionResult, IStatusCodeActionResult
+ public class ContentResult : ActionResult, IResult, IStatusCodeActionResult
{
+ private const string DefaultContentType = "text/plain; charset=utf-8";
+
///
/// Gets or set the content representing the body of the response.
///
@@ -39,5 +45,55 @@ public override Task ExecuteResultAsync(ActionContext context)
var executor = context.HttpContext.RequestServices.GetRequiredService>();
return executor.ExecuteAsync(context, this);
}
+
+ ///
+ /// Writes the content to the HTTP response.
+ ///
+ /// The for the current request.
+ /// A task that represents the asynchronous execute operation.
+ async Task IResult.ExecuteAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ var response = httpContext.Response;
+
+ ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
+ ContentType,
+ response.ContentType,
+ DefaultContentType,
+ out var resolvedContentType,
+ out var resolvedContentTypeEncoding);
+
+ response.ContentType = resolvedContentType;
+
+ if (StatusCode != null)
+ {
+ response.StatusCode = StatusCode.Value;
+ }
+
+ var factory = httpContext.RequestServices.GetRequiredService();
+ var logger = factory.CreateLogger();
+
+ logger.ContentResultExecuting(resolvedContentType);
+
+ if (Content != null)
+ {
+ response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
+
+ await using (var textWriter = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding))
+ {
+ await textWriter.WriteAsync(Content);
+
+ // Flushing the HttpResponseStreamWriter does not flush the underlying stream. This just flushes
+ // the buffered text in the writer.
+ // We do this rather than letting dispose handle it because dispose would call Write and we want
+ // to call WriteAsync.
+ await textWriter.FlushAsync();
+ }
+ }
+ }
}
}
diff --git a/src/Mvc/Mvc.Core/test/ContentResultTest.cs b/src/Mvc/Mvc.Core/test/ContentResultTest.cs
index afe9675a695d..db4e0c66c803 100644
--- a/src/Mvc/Mvc.Core/test/ContentResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/ContentResultTest.cs
@@ -24,7 +24,7 @@ public class ContentResultTest
MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize;
[Fact]
- public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding()
+ public async Task ContentResult_ExecuteResultAsync_Response_NullContent_SetsContentTypeAndEncoding()
{
// Arrange
var contentResult = new ContentResult
@@ -45,6 +45,28 @@ public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding(
MediaTypeAssert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
}
+ [Fact]
+ public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
+ {
+ // Arrange
+ var contentResult = new ContentResult
+ {
+ Content = null,
+ ContentType = new MediaTypeHeaderValue("text/plain")
+ {
+ Encoding = Encoding.Unicode
+ }.ToString()
+ };
+ var httpContext = GetHttpContext();
+ var actionContext = GetActionContext(httpContext);
+
+ // Act
+ await ((IResult)contentResult).ExecuteAsync(httpContext);
+
+ // Assert
+ MediaTypeAssert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
+ }
+
public static TheoryData ContentResultContentTypeData
{
get
@@ -143,6 +165,36 @@ public async Task ContentResult_ExecuteResultAsync_SetContentTypeAndEncoding_OnR
Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
}
+ [Theory]
+ [MemberData(nameof(ContentResultContentTypeData))]
+ public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnResponse(
+ MediaTypeHeaderValue contentType,
+ string content,
+ string responseContentType,
+ string expectedContentType,
+ byte[] expectedContentData)
+ {
+ // Arrange
+ var contentResult = new ContentResult
+ {
+ Content = content,
+ ContentType = contentType?.ToString()
+ };
+ var httpContext = GetHttpContext();
+ var memoryStream = new MemoryStream();
+ httpContext.Response.Body = memoryStream;
+ httpContext.Response.ContentType = responseContentType;
+
+ // Act
+ await ((IResult)contentResult).ExecuteAsync(httpContext);
+
+ // Assert
+ var finalResponseContentType = httpContext.Response.ContentType;
+ Assert.Equal(expectedContentType, finalResponseContentType);
+ Assert.Equal(expectedContentData, memoryStream.ToArray());
+ Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
+ }
+
public static TheoryData ContentResult_WritesDataCorrectly_ForDifferentContentSizesData
{
get
@@ -246,6 +298,31 @@ public async Task ContentResult_WritesDataCorrectly_ForDifferentContentSizes(str
Assert.Equal(content, actualContent);
}
+ [Theory]
+ [MemberData(nameof(ContentResult_WritesDataCorrectly_ForDifferentContentSizesData))]
+ public async Task ContentResult_ExecuteAsync_WritesDataCorrectly_ForDifferentContentSizes(string content, string contentType)
+ {
+ // Arrange
+ var contentResult = new ContentResult
+ {
+ Content = content,
+ ContentType = contentType
+ };
+ var httpContext = GetHttpContext();
+ var memoryStream = new MemoryStream();
+ httpContext.Response.Body = memoryStream;
+ var encoding = MediaTypeHeaderValue.Parse(contentType).Encoding;
+
+ // Act
+ await ((IResult)contentResult).ExecuteAsync(httpContext);
+
+ // Assert
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ var streamReader = new StreamReader(memoryStream, encoding);
+ var actualContent = await streamReader.ReadToEndAsync();
+ Assert.Equal(content, actualContent);
+ }
+
private static ActionContext GetActionContext(HttpContext httpContext)
{
var routeData = new RouteData();
@@ -270,6 +347,7 @@ private static IServiceCollection CreateServices()
services.AddSingleton>(new ContentResultExecutor(
new Logger(NullLoggerFactory.Instance),
new MemoryPoolHttpResponseStreamWriterFactory(ArrayPool.Shared, charArrayPool.Object)));
+ services.AddSingleton(NullLoggerFactory.Instance);
return services;
}