[Fixes #3177] Clean up logic for encoding in ViewResult and friends
This commit is contained in:
parent
cd761a644d
commit
5364468001
|
|
@ -47,32 +47,31 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var logger = loggerFactory.CreateLogger<ContentResult>();
|
var logger = loggerFactory.CreateLogger<ContentResult>();
|
||||||
|
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
var contentTypeHeader = ContentType;
|
|
||||||
|
|
||||||
if (contentTypeHeader != null && contentTypeHeader.Encoding == null)
|
string resolvedContentType;
|
||||||
{
|
Encoding resolvedContentTypeEncoding;
|
||||||
// Do not modify the user supplied content type, so copy it instead
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
contentTypeHeader = contentTypeHeader.Copy();
|
ContentType,
|
||||||
contentTypeHeader.Encoding = Encoding.UTF8;
|
response.ContentType,
|
||||||
}
|
DefaultContentType,
|
||||||
|
out resolvedContentType,
|
||||||
|
out resolvedContentTypeEncoding);
|
||||||
|
|
||||||
response.ContentType = contentTypeHeader?.ToString()
|
response.ContentType = resolvedContentType;
|
||||||
?? response.ContentType
|
|
||||||
?? DefaultContentType.ToString();
|
|
||||||
|
|
||||||
if (StatusCode != null)
|
if (StatusCode != null)
|
||||||
{
|
{
|
||||||
response.StatusCode = StatusCode.Value;
|
response.StatusCode = StatusCode.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.ContentResultExecuting(contentTypeHeader);
|
logger.ContentResultExecuting(resolvedContentType);
|
||||||
|
|
||||||
if (Content != null)
|
if (Content != null)
|
||||||
{
|
{
|
||||||
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
|
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
bufferingFeature?.DisableResponseBuffering();
|
bufferingFeature?.DisableResponseBuffering();
|
||||||
|
|
||||||
return response.WriteAsync(Content, contentTypeHeader?.Encoding ?? DefaultContentType.Encoding);
|
return response.WriteAsync(Content, resolvedContentTypeEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Internal
|
||||||
|
{
|
||||||
|
public static class ResponseContentTypeHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content type and encoding that need to be used for the response.
|
||||||
|
/// The priority for selecting the content type is:
|
||||||
|
/// 1. ContentType property set on the action result
|
||||||
|
/// 2. <see cref="HttpResponse.ContentType"/> property set on <see cref="HttpResponse"/>
|
||||||
|
/// 3. Default content type set on the action result
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The user supplied content type is not modified and is used as is. For example, if user
|
||||||
|
/// sets the content type to be "text/plain" without any encoding, then the default content type's
|
||||||
|
/// encoding is used to write the response and the ContentType header is set to be "text/plain" without any
|
||||||
|
/// "charset" information.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="actionResultContentType">ContentType set on the action result</param>
|
||||||
|
/// <param name="httpResponseContentType"><see cref="HttpResponse.ContentType"/> property set
|
||||||
|
/// on <see cref="HttpResponse"/></param>
|
||||||
|
/// <param name="defaultContentType">The default content type of the action result.</param>
|
||||||
|
/// <param name="resolvedContentType">The content type to be used for the response content type header</param>
|
||||||
|
/// <param name="resolvedContentTypeEncoding">Encoding to be used for writing the response</param>
|
||||||
|
public static void ResolveContentTypeAndEncoding(
|
||||||
|
MediaTypeHeaderValue actionResultContentType,
|
||||||
|
string httpResponseContentType,
|
||||||
|
MediaTypeHeaderValue defaultContentType,
|
||||||
|
out string resolvedContentType,
|
||||||
|
out Encoding resolvedContentTypeEncoding)
|
||||||
|
{
|
||||||
|
Debug.Assert(defaultContentType != null);
|
||||||
|
Debug.Assert(defaultContentType.Encoding != null);
|
||||||
|
|
||||||
|
// 1. User sets the ContentType property on the action result
|
||||||
|
if (actionResultContentType != null)
|
||||||
|
{
|
||||||
|
resolvedContentType = actionResultContentType.ToString();
|
||||||
|
resolvedContentTypeEncoding = actionResultContentType.Encoding ?? defaultContentType.Encoding;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. User sets the ContentType property on the http response directly
|
||||||
|
if (!string.IsNullOrEmpty(httpResponseContentType))
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue mediaType;
|
||||||
|
if (MediaTypeHeaderValue.TryParse(httpResponseContentType, out mediaType))
|
||||||
|
{
|
||||||
|
resolvedContentType = httpResponseContentType;
|
||||||
|
resolvedContentTypeEncoding = mediaType.Encoding ?? defaultContentType.Encoding;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedContentType = httpResponseContentType;
|
||||||
|
resolvedContentTypeEncoding = defaultContentType.Encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fall-back to the default content type
|
||||||
|
resolvedContentType = defaultContentType.ToString();
|
||||||
|
resolvedContentTypeEncoding = defaultContentType.Encoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,9 +19,9 @@ namespace Microsoft.AspNet.Mvc.Logging
|
||||||
"Executing ContentResult with HTTP Response ContentType of {ContentType}");
|
"Executing ContentResult with HTTP Response ContentType of {ContentType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ContentResultExecuting(this ILogger logger, MediaTypeHeaderValue contentType)
|
public static void ContentResultExecuting(this ILogger logger, string contentType)
|
||||||
{
|
{
|
||||||
_contentResultExecuting(logger, contentType?.MediaType, null);
|
_contentResultExecuting(logger, contentType, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.OptionsModel;
|
using Microsoft.Extensions.OptionsModel;
|
||||||
|
|
@ -79,22 +80,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
|
|
||||||
var contentTypeHeader = ContentType;
|
string resolvedContentType = null;
|
||||||
if (contentTypeHeader == null)
|
Encoding resolvedContentTypeEncoding = null;
|
||||||
{
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
contentTypeHeader = DefaultContentType;
|
ContentType,
|
||||||
}
|
response.ContentType,
|
||||||
else
|
DefaultContentType,
|
||||||
{
|
out resolvedContentType,
|
||||||
if (contentTypeHeader.Encoding == null)
|
out resolvedContentTypeEncoding);
|
||||||
{
|
|
||||||
// Do not modify the user supplied content type, so copy it instead
|
|
||||||
contentTypeHeader = contentTypeHeader.Copy();
|
|
||||||
contentTypeHeader.Encoding = Encoding.UTF8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.ContentType = contentTypeHeader.ToString();
|
response.ContentType = resolvedContentType;
|
||||||
|
|
||||||
if (StatusCode != null)
|
if (StatusCode != null)
|
||||||
{
|
{
|
||||||
|
|
@ -113,8 +108,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.JsonResultExecuting(Value);
|
logger.JsonResultExecuting(Value);
|
||||||
|
using (var writer = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding))
|
||||||
using (var writer = new HttpResponseStreamWriter(response.Body, contentTypeHeader.Encoding))
|
|
||||||
{
|
{
|
||||||
using (var jsonWriter = new JsonTextWriter(writer))
|
using (var jsonWriter = new JsonTextWriter(writer))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Logging;
|
using Microsoft.AspNet.Mvc.Logging;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
@ -90,32 +91,23 @@ namespace Microsoft.AspNet.Mvc
|
||||||
tempData = services.GetRequiredService<ITempDataDictionary>();
|
tempData = services.GetRequiredService<ITempDataDictionary>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentType = ContentType;
|
string resolvedContentType = null;
|
||||||
if (contentType != null && contentType.Encoding == null)
|
Encoding resolvedContentTypeEncoding = null;
|
||||||
{
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
// Do not modify the user supplied content type, so copy it instead
|
ContentType,
|
||||||
contentType = contentType.Copy();
|
response.ContentType,
|
||||||
contentType.Encoding = Encoding.UTF8;
|
ViewExecutor.DefaultContentType,
|
||||||
}
|
out resolvedContentType,
|
||||||
|
out resolvedContentTypeEncoding);
|
||||||
|
|
||||||
// Priority list for setting content-type/encoding:
|
response.ContentType = resolvedContentType;
|
||||||
// 1. this.ContentType (set by the user on the result)
|
|
||||||
// 2. response.ContentType (likely set by the user in controller code)
|
|
||||||
// 3. ViewExecutor.DefaultContentType (sensible default)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
response.ContentType =
|
|
||||||
contentType?.ToString() ??
|
|
||||||
response.ContentType ??
|
|
||||||
ViewExecutor.DefaultContentType.ToString();
|
|
||||||
|
|
||||||
if (StatusCode != null)
|
if (StatusCode != null)
|
||||||
{
|
{
|
||||||
response.StatusCode = StatusCode.Value;
|
response.StatusCode = StatusCode.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encoding = contentType?.Encoding ?? ViewExecutor.DefaultContentType?.Encoding;
|
using (var writer = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding))
|
||||||
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
|
|
||||||
{
|
{
|
||||||
var viewContext = new ViewContext(
|
var viewContext = new ViewContext(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Diagnostics;
|
using Microsoft.AspNet.Mvc.Diagnostics;
|
||||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
|
|
@ -96,7 +97,8 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
|
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
|
||||||
/// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param>
|
/// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param>
|
||||||
/// <param name="contentType">
|
/// <param name="contentType">
|
||||||
/// The content-type header value to set in the response. If <c>null</c>, <see cref="DefaultContentType"/> will be used.
|
/// The content-type header value to set in the response. If <c>null</c>,
|
||||||
|
/// <see cref="DefaultContentType"/> will be used.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="statusCode">
|
/// <param name="statusCode">
|
||||||
/// The HTTP status code to set in the response. May be <c>null</c>.
|
/// The HTTP status code to set in the response. May be <c>null</c>.
|
||||||
|
|
@ -134,26 +136,23 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
|
|
||||||
var response = actionContext.HttpContext.Response;
|
var response = actionContext.HttpContext.Response;
|
||||||
|
|
||||||
if (contentType != null && contentType.Encoding == null)
|
string resolvedContentType = null;
|
||||||
{
|
Encoding resolvedContentTypeEncoding = null;
|
||||||
// Do not modify the user supplied content type, so copy it instead
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
contentType = contentType.Copy();
|
contentType,
|
||||||
contentType.Encoding = Encoding.UTF8;
|
response.ContentType,
|
||||||
}
|
DefaultContentType,
|
||||||
|
out resolvedContentType,
|
||||||
|
out resolvedContentTypeEncoding);
|
||||||
|
|
||||||
// Priority list for setting content-type:
|
response.ContentType = resolvedContentType;
|
||||||
// 1. passed in contentType (likely set by the user on the result)
|
|
||||||
// 2. response.ContentType (likely set by the user in controller code)
|
|
||||||
// 3. ViewExecutor.DefaultContentType (sensible default)
|
|
||||||
response.ContentType = contentType?.ToString() ?? response.ContentType ?? DefaultContentType.ToString();
|
|
||||||
|
|
||||||
if (statusCode != null)
|
if (statusCode != null)
|
||||||
{
|
{
|
||||||
response.StatusCode = statusCode.Value;
|
response.StatusCode = statusCode.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encoding = contentType?.Encoding ?? DefaultContentType.Encoding;
|
using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
|
||||||
using (var writer = WriterFactory.CreateWriter(response.Body, encoding))
|
|
||||||
{
|
{
|
||||||
var viewContext = new ViewContext(
|
var viewContext = new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
@ -162,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
tempData,
|
tempData,
|
||||||
writer,
|
writer,
|
||||||
ViewOptions.HtmlHelperOptions);
|
ViewOptions.HtmlHelperOptions);
|
||||||
|
|
||||||
DiagnosticSource.BeforeView(view, viewContext);
|
DiagnosticSource.BeforeView(view, viewContext);
|
||||||
|
|
||||||
await view.RenderAsync(viewContext);
|
await view.RenderAsync(viewContext);
|
||||||
|
|
|
||||||
|
|
@ -22,47 +22,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class ContentResultTest
|
public class ContentResultTest
|
||||||
{
|
{
|
||||||
[Fact]
|
|
||||||
public async Task ContentResult_Response_NullEncoding_SetsContentTypeAndDefaultEncoding()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var contentResult = new ContentResult
|
|
||||||
{
|
|
||||||
Content = "Test Content",
|
|
||||||
ContentType = new MediaTypeHeaderValue("application/json")
|
|
||||||
};
|
|
||||||
var httpContext = GetHttpContext();
|
|
||||||
var actionContext = GetActionContext(httpContext);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await contentResult.ExecuteResultAsync(actionContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ContentResult_Response_SetsContentTypeAndEncoding()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var contentResult = new ContentResult
|
|
||||||
{
|
|
||||||
Content = "Test Content",
|
|
||||||
ContentType = new MediaTypeHeaderValue("text/plain")
|
|
||||||
{
|
|
||||||
Encoding = Encoding.ASCII
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var httpContext = GetHttpContext();
|
|
||||||
var actionContext = GetActionContext(httpContext);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await contentResult.ExecuteResultAsync(actionContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("text/plain; charset=us-ascii", httpContext.Response.ContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding()
|
public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding()
|
||||||
{
|
{
|
||||||
|
|
@ -118,6 +77,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
// contentType, content, responseContentType, expectedContentType, expectedData
|
||||||
return new TheoryData<MediaTypeHeaderValue, string, string, string, byte[]>
|
return new TheoryData<MediaTypeHeaderValue, string, string, string, byte[]>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
@ -131,14 +91,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
new MediaTypeHeaderValue("text/foo"),
|
new MediaTypeHeaderValue("text/foo"),
|
||||||
"κόσμε",
|
"κόσμε",
|
||||||
null,
|
null,
|
||||||
"text/foo; charset=utf-8",
|
"text/foo",
|
||||||
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
|
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
|
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
|
||||||
"κόσμε",
|
"κόσμε",
|
||||||
null,
|
null,
|
||||||
"text/foo; p1=p1-value; charset=utf-8",
|
"text/foo; p1=p1-value",
|
||||||
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
|
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Internal
|
||||||
|
{
|
||||||
|
public class ResponseContentTypeHelperTest
|
||||||
|
{
|
||||||
|
public static TheoryData<MediaTypeHeaderValue, string, string> ResponseContentTypeData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// contentType, responseContentType, expectedContentType
|
||||||
|
return new TheoryData<MediaTypeHeaderValue, string, string>
|
||||||
|
{
|
||||||
|
// No explicit content type is provided, fall-back to the default content type
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"text/default; p1=p1-value; charset=utf-8"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly without encoding on action result. No charset parameter added in
|
||||||
|
// expected content type
|
||||||
|
{
|
||||||
|
new MediaTypeHeaderValue("text/foo"),
|
||||||
|
null,
|
||||||
|
"text/foo"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly with encoding on action result. Expected content type
|
||||||
|
// has the charset
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
|
null,
|
||||||
|
"text/foo; charset=us-ascii"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly without encoding and additional parameters on action result
|
||||||
|
// Expected content type has the additional parameters but with no charset.
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
||||||
|
null,
|
||||||
|
"text/foo; p1=p1-value"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly with encoding and additional parameters on action result
|
||||||
|
// Expected content type has the additional parameters and the charset.
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value; charset=us-ascii"),
|
||||||
|
null,
|
||||||
|
"text/foo; p1=p1-value; charset=us-ascii"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly without encoding on http response.
|
||||||
|
// No charset parameter added in expected content type
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
"text/bar",
|
||||||
|
"text/bar"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly without encoding and additional parameters on http response
|
||||||
|
// No charset parameter added in expected content type
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
"text/bar; p1=p1-value",
|
||||||
|
"text/bar; p1=p1-value"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type is set explicitly with encoding and additional parameters on http response
|
||||||
|
// Expected content type has charset and additional parameters
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
"text/bar; p1=p1-value; charset=us-ascii",
|
||||||
|
"text/bar; p1=p1-value; charset=us-ascii"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content type set on action result takes precedence over the conten type set on http response
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
|
"text/bar",
|
||||||
|
"text/foo; charset=us-ascii"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
|
"text/bar; charset=utf-8",
|
||||||
|
"text/foo; charset=us-ascii"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ResponseContentTypeData))]
|
||||||
|
public void GetsExpectedContentTypeAndEncoding(
|
||||||
|
MediaTypeHeaderValue contentType,
|
||||||
|
string responseContentType,
|
||||||
|
string expectedContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var defaultContentType = MediaTypeHeaderValue.Parse("text/default; p1=p1-value; charset=utf-8");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string resolvedContentType = null;
|
||||||
|
Encoding resolvedContentTypeEncoding = null;
|
||||||
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
|
contentType,
|
||||||
|
responseContentType,
|
||||||
|
defaultContentType,
|
||||||
|
out resolvedContentType,
|
||||||
|
out resolvedContentTypeEncoding);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContentType, resolvedContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoesNotThrowException_OnInvalidResponseContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "invalid-content-type";
|
||||||
|
var defaultContentType = MediaTypeHeaderValue.Parse("text/plain; charset=utf-8");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string resolvedContentType = null;
|
||||||
|
Encoding resolvedContentTypeEncoding = null;
|
||||||
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
|
null,
|
||||||
|
expectedContentType,
|
||||||
|
defaultContentType,
|
||||||
|
out resolvedContentType,
|
||||||
|
out resolvedContentTypeEncoding);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContentType, resolvedContentType);
|
||||||
|
Assert.Equal(Encoding.UTF8, resolvedContentTypeEncoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteResultAsync_NullEncoding_SetsContentTypeAndDefaultEncoding()
|
public async Task ExecuteResultAsync_NullEncoding_DoesNotSetCharsetOnContentType()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = _abcdUTF8Bytes;
|
var expected = _abcdUTF8Bytes;
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, written);
|
Assert.Equal(expected, written);
|
||||||
Assert.Equal("text/json; charset=utf-8", context.Response.ContentType);
|
Assert.Equal("text/json", context.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -89,6 +89,52 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal("text/json; charset=us-ascii", context.Response.ContentType);
|
Assert.Equal("text/json; charset=us-ascii", context.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoResultContentTypeSet_UsesResponseContentType_AndSuppliedEncoding()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedData = Encoding.ASCII.GetBytes("{\"foo\":\"abcd\"}");
|
||||||
|
var expectedContentType = "text/foo; p1=p1-value; charset=us-ascii";
|
||||||
|
var context = GetHttpContext();
|
||||||
|
context.Response.ContentType = expectedContentType;
|
||||||
|
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
var result = new JsonResult(new { foo = "abcd" });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
var written = GetWrittenBytes(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedData, written);
|
||||||
|
Assert.Equal(expectedContentType, context.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("text/foo", "text/foo")]
|
||||||
|
[InlineData("text/foo; p1=p1-value", "text/foo; p1=p1-value")]
|
||||||
|
public async Task NoResultContentTypeSet_UsesResponseContentTypeAndDefaultEncoding_DoesNotSetCharset(
|
||||||
|
string responseContentType,
|
||||||
|
string expectedContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = _abcdUTF8Bytes;
|
||||||
|
|
||||||
|
var context = GetHttpContext();
|
||||||
|
context.Response.ContentType = responseContentType;
|
||||||
|
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
var result = new JsonResult(new { foo = "abcd" });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
var written = GetWrittenBytes(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, written);
|
||||||
|
Assert.Equal(expectedContentType, context.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<byte> AbcdIndentedUTF8Bytes
|
private static List<byte> AbcdIndentedUTF8Bytes
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ContentResult_WritesContent_SetsContentTypeWithDefaultEncoding()
|
public async Task ContentResult_WritesContent_SetsContentType_UsesDefaultEncoding_AndNoCharset()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
|
|
@ -214,7 +214,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||||
Assert.Equal("utf-8", response.Content.Headers.ContentType.CharSet);
|
|
||||||
Assert.Equal("content", await response.Content.ReadAsStringAsync());
|
Assert.Equal("content", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult
|
var viewComponentResult = new ViewComponentResult
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new object[] { "World!" },
|
||||||
|
|
@ -338,11 +338,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo"),
|
new MediaTypeHeaderValue("text/foo"),
|
||||||
"text/foo; charset=utf-8"
|
"text/foo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
|
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
|
||||||
"text/foo; p1=p1-value; charset=utf-8"
|
"text/foo; p1=p1-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
|
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
|
||||||
|
|
@ -422,8 +422,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType);
|
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public async Task ViewComponentResult_NoContentTypeSet_PreservesResponseContentType()
|
[InlineData("text/foo", "text/foo; charset=utf-8")]
|
||||||
|
[InlineData("text/foo; p1=p1-value", "text/foo; p1=p1-value; charset=utf-8")]
|
||||||
|
[InlineData("text/foo; p1=p1-value; charset=us-ascii", "text/foo; p1=p1-value; charset=us-ascii")]
|
||||||
|
public async Task ViewComponentResult_NoContentTypeSet_PreservesResponseContentType(
|
||||||
|
string responseContentType,
|
||||||
|
string expectedContentType)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new ViewComponentDescriptor()
|
var descriptor = new ViewComponentDescriptor()
|
||||||
|
|
@ -435,7 +440,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var expectedContentType = "application/x-will-not-be-overridden";
|
|
||||||
actionContext.HttpContext.Response.ContentType = expectedContentType;
|
actionContext.HttpContext.Response.ContentType = expectedContentType;
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
|
|
@ -453,7 +457,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors)
|
private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors)
|
||||||
{
|
{
|
||||||
var httpContext = new HttpContextAccessor() { HttpContext = context };
|
var httpContext = new HttpContextAccessor() { HttpContext = context };
|
||||||
var tempDataProvider = new SessionStateTempDataProvider();
|
var tempDataProvider = new SessionStateTempDataProvider();
|
||||||
var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
|
var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindView_WritesDiagnostic_ViewFound()
|
public void FindView_WritesDiagnostic_ViewFound()
|
||||||
{
|
{
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("application/x-my-content-type; charset=utf-8", context.HttpContext.Response.ContentType);
|
Assert.Equal("application/x-my-content-type", context.HttpContext.Response.ContentType);
|
||||||
|
|
||||||
// Check if the original instance provided by the user has not changed.
|
// Check if the original instance provided by the user has not changed.
|
||||||
// Since we do not have access to the new instance created within the view executor,
|
// Since we do not have access to the new instance created within the view executor,
|
||||||
|
|
@ -199,7 +199,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialViewResultExecutor GetViewExecutor(DiagnosticSource diagnosticSource = null)
|
private PartialViewResultExecutor GetViewExecutor(DiagnosticSource diagnosticSource = null)
|
||||||
{
|
{
|
||||||
if (diagnosticSource == null)
|
if (diagnosticSource == null)
|
||||||
|
|
|
||||||
|
|
@ -38,17 +38,22 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo"),
|
new MediaTypeHeaderValue("text/foo"),
|
||||||
null,
|
null,
|
||||||
"text/foo; charset=utf-8"
|
"text/foo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
|
null,
|
||||||
|
"text/foo; charset=us-ascii"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
||||||
null,
|
null,
|
||||||
"text/foo; p1=p1-value; charset=utf-8"
|
"text/foo; p1=p1-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value; charset=us-ascii"),
|
||||||
null,
|
null,
|
||||||
"text/foo; charset=us-ascii"
|
"text/foo; p1=p1-value; charset=us-ascii"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
|
|
@ -57,19 +62,24 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
"application/xml; charset=us-ascii",
|
"text/bar; p1=p1-value",
|
||||||
"application/xml; charset=us-ascii"
|
"text/bar; p1=p1-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
"Invalid content type",
|
"text/bar; p1=p1-value; charset=us-ascii",
|
||||||
"Invalid content type"
|
"text/bar; p1=p1-value; charset=us-ascii"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
"text/bar",
|
"text/bar",
|
||||||
"text/foo; charset=us-ascii"
|
"text/foo; charset=us-ascii"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue.Parse("text/foo; charset=us-ascii"),
|
||||||
|
"text/bar; charset=utf-8",
|
||||||
|
"text/foo; charset=us-ascii"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +160,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
context,
|
context,
|
||||||
new RouteData(),
|
new RouteData(),
|
||||||
new ActionDescriptor());
|
new ActionDescriptor());
|
||||||
|
|
||||||
context.RequestServices = GetServiceProvider();
|
context.RequestServices = GetServiceProvider();
|
||||||
var viewExecutor = CreateViewExecutor();
|
var viewExecutor = CreateViewExecutor();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("application/x-my-content-type; charset=utf-8", context.HttpContext.Response.ContentType);
|
Assert.Equal("application/x-my-content-type", context.HttpContext.Response.ContentType);
|
||||||
|
|
||||||
// Check if the original instance provided by the user has not changed.
|
// Check if the original instance provided by the user has not changed.
|
||||||
// Since we do not have access to the new instance created within the view executor,
|
// Since we do not have access to the new instance created within the view executor,
|
||||||
|
|
@ -199,7 +199,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewResultExecutor GetViewExecutor(DiagnosticListener diagnosticSource = null)
|
private ViewResultExecutor GetViewExecutor(DiagnosticListener diagnosticSource = null)
|
||||||
{
|
{
|
||||||
if (diagnosticSource == null)
|
if (diagnosticSource == null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue