[Fixes #4945] Simple string returned by controller action is not a valid JSON!
This commit is contained in:
parent
8a74148fe1
commit
8ac6b6699f
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Primitives;
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Always writes a string value to the response, regardless of requested content type.
|
/// A <see cref="TextOutputFormatter"/> for simple text content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringOutputFormatter : TextOutputFormatter
|
public class StringOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
|
|
@ -29,18 +29,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore the passed in content type, if the object is string
|
|
||||||
// always return it as a text/plain format.
|
|
||||||
if (context.ObjectType == typeof(string) || context.Object is string)
|
if (context.ObjectType == typeof(string) || context.Object is string)
|
||||||
{
|
{
|
||||||
if (!context.ContentType.HasValue)
|
// Call into base to check if the current request's content type is a supported media type.
|
||||||
{
|
return base.CanWriteResult(context);
|
||||||
var mediaType = SupportedMediaTypes[0];
|
|
||||||
var encoding = SupportedEncodings[0];
|
|
||||||
context.ContentType = new StringSegment(MediaType.ReplaceEncoding(mediaType, encoding));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -12,39 +12,52 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public class TextPlainFormatterTests
|
public class StringOutputFormatterTests
|
||||||
{
|
{
|
||||||
public static IEnumerable<object[]> OutputFormatterContextValues
|
public static IEnumerable<object[]> CanWriteStringsData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult
|
// object value, bool useDeclaredTypeAsString
|
||||||
yield return new object[] { "valid value", true, true };
|
yield return new object[] { "declared and runtime type are same", true };
|
||||||
yield return new object[] { null, true, true };
|
yield return new object[] { "declared and runtime type are different", false };
|
||||||
yield return new object[] { null, false, false };
|
yield return new object[] { null, true };
|
||||||
yield return new object[] { new object(), false, false };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public static TheoryData<object> CannotWriteNonStringsData
|
||||||
public void CanWriteResult_SetsAcceptContentType()
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<object>()
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
new object()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
public void CannotWriteUnsupportedMediaType(string contentType)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var formatter = new StringOutputFormatter();
|
var formatter = new StringOutputFormatter();
|
||||||
var expectedContentType = new StringSegment("application/json");
|
var expectedContentType = new StringSegment(contentType);
|
||||||
|
|
||||||
var context = new OutputFormatterWriteContext(
|
var context = new OutputFormatterWriteContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
typeof(string),
|
typeof(string),
|
||||||
"Thisisastring");
|
"Thisisastring");
|
||||||
context.ContentType = expectedContentType;
|
context.ContentType = new StringSegment(contentType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = formatter.CanWriteResult(context);
|
var result = formatter.CanWriteResult(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.False(result);
|
||||||
Assert.Equal(expectedContentType, context.ContentType);
|
Assert.Equal(expectedContentType, context.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +66,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var formatter = new StringOutputFormatter();
|
var formatter = new StringOutputFormatter();
|
||||||
|
|
||||||
var context = new OutputFormatterWriteContext(
|
var context = new OutputFormatterWriteContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
|
@ -65,18 +77,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Equal(new StringSegment("text/plain; charset=utf-8"), context.ContentType);
|
Assert.Equal(new StringSegment("text/plain"), context.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(OutputFormatterContextValues))]
|
[MemberData(nameof(CanWriteStringsData))]
|
||||||
public void CanWriteResult_ReturnsTrueForStringTypes(
|
public void CanWriteStrings(
|
||||||
object value,
|
object value,
|
||||||
bool useDeclaredTypeAsString,
|
bool useDeclaredTypeAsString)
|
||||||
bool expectedCanWriteResult)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedContentType = new StringSegment("application/json");
|
var expectedContentType = new StringSegment("text/plain");
|
||||||
|
|
||||||
var formatter = new StringOutputFormatter();
|
var formatter = new StringOutputFormatter();
|
||||||
var type = useDeclaredTypeAsString ? typeof(string) : typeof(object);
|
var type = useDeclaredTypeAsString ? typeof(string) : typeof(object);
|
||||||
|
|
@ -86,13 +97,35 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
type,
|
type,
|
||||||
value);
|
value);
|
||||||
context.ContentType = expectedContentType;
|
context.ContentType = new StringSegment("text/plain");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = formatter.CanWriteResult(context);
|
var result = formatter.CanWriteResult(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedCanWriteResult, result);
|
Assert.True(result);
|
||||||
|
Assert.Equal(expectedContentType, context.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CannotWriteNonStringsData))]
|
||||||
|
public void CannotWriteNonStrings(object value)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = new StringSegment("text/plain");
|
||||||
|
var formatter = new StringOutputFormatter();
|
||||||
|
var context = new OutputFormatterWriteContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
typeof(object),
|
||||||
|
value);
|
||||||
|
context.ContentType = new StringSegment("text/plain");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanWriteResult(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
Assert.Equal(expectedContentType, context.ContentType);
|
Assert.Equal(expectedContentType, context.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -345,33 +345,31 @@ END:VCARD
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(null)]
|
||||||
[InlineData(false)]
|
[InlineData("text/plain")]
|
||||||
public async Task ObjectResult_WithStringReturnType_DefaultToTextPlain(bool matchFormatterOnObjectType)
|
[InlineData("text/plain; charset=utf-8")]
|
||||||
|
[InlineData("text/html, application/xhtml+xml, image/jxr, */*")] // typical browser accept header
|
||||||
|
public async Task ObjectResult_WithStringReturnType_DefaultToTextPlain(string acceptMediaType)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var targetUri = "http://localhost/FallbackOnTypeBasedMatch/ReturnString?matchFormatterOnObjectType=true" +
|
var request = new HttpRequestMessage(HttpMethod.Get, "FallbackOnTypeBasedMatch/ReturnString");
|
||||||
matchFormatterOnObjectType;
|
request.Headers.Accept.ParseAdd(acceptMediaType);
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
|
Assert.Equal("text/plain; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
var actualBody = await response.Content.ReadAsStringAsync();
|
var actualBody = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal("Hello World!", actualBody);
|
Assert.Equal("Hello World!", actualBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(true)]
|
public async Task ObjectResult_WithStringReturnType_AndNonTextPlainMediaType_DoesNotReturnTextPlain()
|
||||||
[InlineData(false)]
|
|
||||||
public async Task ObjectResult_WithStringReturnType_SetsMediaTypeToAccept(bool matchFormatterOnObjectType)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var targetUri = "http://localhost/FallbackOnTypeBasedMatch/ReturnString?matchFormatterOnObjectType=" +
|
var targetUri = "http://localhost/FallbackOnTypeBasedMatch/ReturnString";
|
||||||
matchFormatterOnObjectType;
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
|
var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
|
|
@ -380,9 +378,9 @@ END:VCARD
|
||||||
|
|
||||||
// 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; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
var actualBody = await response.Content.ReadAsStringAsync();
|
var actualBody = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal("Hello World!", actualBody);
|
Assert.Equal("\"Hello World!\"", actualBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// These tests are for scenarios when <see cref="MvcOptions.RespectBrowserAcceptHeader"/> is <c>False</c>, which is the default.
|
||||||
|
/// </summary>
|
||||||
|
public class DoNotRespectBrowserAcceptHeaderTests : IClassFixture<MvcTestFixture<FormatterWebSite.Startup>>
|
||||||
|
{
|
||||||
|
public DoNotRespectBrowserAcceptHeaderTests(MvcTestFixture<FormatterWebSite.Startup> fixture)
|
||||||
|
{
|
||||||
|
Client = fixture.Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
|
[InlineData("application/xml,*/*")]
|
||||||
|
public async Task AllMediaRangeAcceptHeader_FirstFormatterInListWritesResponse(string acceptHeader)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = RequestWithAccept("http://localhost/DoNotRespectBrowserAcceptHeader/EmployeeInfo", acceptHeader);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
|
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("{\"id\":10,\"name\":\"John\"}", responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
|
[InlineData("application/xml,*/*")]
|
||||||
|
public async Task AllMediaRangeAcceptHeader_ProducesAttributeIsHonored(string acceptHeader)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = RequestWithAccept(
|
||||||
|
"http://localhost/DoNotRespectBrowserAcceptHeader/EmployeeInfoWithProduces",
|
||||||
|
acceptHeader);
|
||||||
|
var expectedResponseData =
|
||||||
|
"<DoNotRespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>20</Id><Name>Mike" +
|
||||||
|
"</Name></DoNotRespectBrowserAcceptHeaderController.Employee>";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
|
Assert.Equal("application/xml; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
XmlAssert.Equal(expectedResponseData, responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
|
[InlineData("application/xml,*/*")]
|
||||||
|
public async Task AllMediaRangeAcceptHeader_WithContentTypeHeader_ContentTypeIsIgnored(string acceptHeader)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestData =
|
||||||
|
"<DoNotRespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>35</Id><Name>Jimmy" +
|
||||||
|
"</Name></DoNotRespectBrowserAcceptHeaderController.Employee>";
|
||||||
|
var expectedResponseData = @"{""id"":35,""name"":""Jimmy""}";
|
||||||
|
var request = RequestWithAccept("http://localhost/DoNotRespectBrowserAcceptHeader/CreateEmployee", acceptHeader);
|
||||||
|
request.Content = new StringContent(requestData, Encoding.UTF8, "application/xml");
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
|
|
||||||
|
// Site uses default output formatter (ignores Accept header) because that header contained a wildcard match.
|
||||||
|
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedResponseData, responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
|
[InlineData("application/xml,application/json;q=0.2")]
|
||||||
|
[InlineData("application/xml,application/json")]
|
||||||
|
public async Task AllMediaRangeAcceptHeader_WithExactMatch_ReturnsExpectedContent(string acceptHeader)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestData =
|
||||||
|
"<DoNotRespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>35</Id><Name>Jimmy" +
|
||||||
|
"</Name></DoNotRespectBrowserAcceptHeaderController.Employee>";
|
||||||
|
var request = RequestWithAccept("http://localhost/DoNotRespectBrowserAcceptHeader/CreateEmployee", acceptHeader);
|
||||||
|
request.Content = new StringContent(requestData, Encoding.UTF8, "application/xml");
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
|
Assert.Equal("application/xml; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(requestData, responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpRequestMessage RequestWithAccept(string url, string accept)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.Add("Accept", accept);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,31 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
public class RespectBrowserAcceptHeaderTests : IClassFixture<MvcTestFixture<FormatterWebSite.Startup>>
|
/// <summary>
|
||||||
|
/// These tests are for scenarios when <see cref="MvcOptions.RespectBrowserAcceptHeader"/> is <c>True</c>(default is False).
|
||||||
|
/// </summary>
|
||||||
|
public class RespectBrowserAcceptHeaderTests : IClassFixture<MvcTestFixture<FormatterWebSite.StartupWithRespectBrowserAcceptHeader>>
|
||||||
{
|
{
|
||||||
public RespectBrowserAcceptHeaderTests(MvcTestFixture<FormatterWebSite.Startup> fixture)
|
public RespectBrowserAcceptHeaderTests(MvcTestFixture<FormatterWebSite.StartupWithRespectBrowserAcceptHeader> fixture)
|
||||||
{
|
{
|
||||||
Client = fixture.Client;
|
Client = fixture.Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData("application/xml,*/*;q=0.2")]
|
public async Task ReturnStringFromAction_StringOutputFormatterDoesNotWriteTheResponse()
|
||||||
[InlineData("application/xml,*/*")]
|
|
||||||
public async Task AllMediaRangeAcceptHeader_FirstFormatterInListWritesResponse(string acceptHeader)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = RequestWithAccept("http://localhost/RespectBrowserAcceptHeader/EmployeeInfo", acceptHeader);
|
var request = new HttpRequestMessage(HttpMethod.Get, "RespectBrowserAcceptHeader/ReturnString");
|
||||||
|
request.Headers.Accept.ParseAdd("text/html, application/json, image/jpeg, */*; q=.2");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -37,24 +36,15 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.NotNull(response.Content.Headers.ContentType);
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
var responseData = await response.Content.ReadAsStringAsync();
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal("{\"id\":10,\"name\":\"John\"}", responseData);
|
Assert.Equal("\"Hello World!\"", responseData);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalTheory]
|
[Fact]
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
public async Task ReturnStringFromAction_AcceptHeaderWithTextPlain_WritesTextPlainResponse()
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
[InlineData("application/xml,*/*;q=0.2")]
|
|
||||||
[InlineData("application/xml,*/*")]
|
|
||||||
public async Task AllMediaRangeAcceptHeader_ProducesAttributeIsHonored(string acceptHeader)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = RequestWithAccept(
|
var request = new HttpRequestMessage(HttpMethod.Get, "RespectBrowserAcceptHeader/ReturnString");
|
||||||
"http://localhost/RespectBrowserAcceptHeader/EmployeeInfoWithProduces",
|
request.Headers.Accept.ParseAdd("text/plain; charset=utf-8");
|
||||||
acceptHeader);
|
|
||||||
var expectedResponseData =
|
|
||||||
"<RespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
|
||||||
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>20</Id><Name>Mike" +
|
|
||||||
"</Name></RespectBrowserAcceptHeaderController.Employee>";
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -63,77 +53,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(response.Content);
|
Assert.NotNull(response.Content);
|
||||||
Assert.NotNull(response.Content.Headers.ContentType);
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
Assert.Equal("application/xml; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
Assert.Equal("text/plain; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
var responseData = await response.Content.ReadAsStringAsync();
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
XmlAssert.Equal(expectedResponseData, responseData);
|
Assert.Equal("Hello World!", responseData);
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
[InlineData("application/xml,*/*;q=0.2")]
|
|
||||||
[InlineData("application/xml,*/*")]
|
|
||||||
public async Task AllMediaRangeAcceptHeader_WithContentTypeHeader_ContentTypeIsIgnored(string acceptHeader)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var requestData =
|
|
||||||
"<RespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
|
||||||
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>35</Id><Name>Jimmy" +
|
|
||||||
"</Name></RespectBrowserAcceptHeaderController.Employee>";
|
|
||||||
var expectedResponseData = @"{""id"":35,""name"":""Jimmy""}";
|
|
||||||
var request = RequestWithAccept("http://localhost/RespectBrowserAcceptHeader/CreateEmployee", acceptHeader);
|
|
||||||
request.Content = new StringContent(requestData, Encoding.UTF8, "application/xml");
|
|
||||||
request.Method = HttpMethod.Post;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var response = await Client.SendAsync(request);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.NotNull(response.Content);
|
|
||||||
Assert.NotNull(response.Content.Headers.ContentType);
|
|
||||||
|
|
||||||
// Site uses default output formatter (ignores Accept header) because that header contained a wildcard match.
|
|
||||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
|
||||||
|
|
||||||
var responseData = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(expectedResponseData, responseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
[InlineData("application/xml,application/json;q=0.2")]
|
|
||||||
[InlineData("application/xml,application/json")]
|
|
||||||
public async Task AllMediaRangeAcceptHeader_WithExactMatch_ReturnsExpectedContent(string acceptHeader)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var requestData =
|
|
||||||
"<RespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
|
||||||
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>35</Id><Name>Jimmy" +
|
|
||||||
"</Name></RespectBrowserAcceptHeaderController.Employee>";
|
|
||||||
var request = RequestWithAccept("http://localhost/RespectBrowserAcceptHeader/CreateEmployee", acceptHeader);
|
|
||||||
request.Content = new StringContent(requestData, Encoding.UTF8, "application/xml");
|
|
||||||
request.Method = HttpMethod.Post;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var response = await Client.SendAsync(request);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.NotNull(response.Content);
|
|
||||||
Assert.NotNull(response.Content.Headers.ContentType);
|
|
||||||
Assert.Equal("application/xml; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
|
||||||
var responseData = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(requestData, responseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpRequestMessage RequestWithAccept(string url, string accept)
|
|
||||||
{
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
request.Headers.Add("Accept", accept);
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class DoNotRespectBrowserAcceptHeaderController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public Employee EmployeeInfo()
|
||||||
|
{
|
||||||
|
return new Employee()
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
Name = "John"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/xml")]
|
||||||
|
public Employee EmployeeInfoWithProduces()
|
||||||
|
{
|
||||||
|
return new Employee()
|
||||||
|
{
|
||||||
|
Id = 20,
|
||||||
|
Name = "Mike"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult CreateEmployee([FromBody]Employee employee)
|
||||||
|
{
|
||||||
|
if(!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ObjectResult(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Employee
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
@ -8,42 +8,9 @@ namespace FormatterWebSite.Controllers
|
||||||
public class RespectBrowserAcceptHeaderController : Controller
|
public class RespectBrowserAcceptHeaderController : Controller
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public Employee EmployeeInfo()
|
public string ReturnString()
|
||||||
{
|
{
|
||||||
return new Employee()
|
return "Hello World!";
|
||||||
{
|
|
||||||
Id = 10,
|
|
||||||
Name = "John"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Produces("application/xml")]
|
|
||||||
public Employee EmployeeInfoWithProduces()
|
|
||||||
{
|
|
||||||
return new Employee()
|
|
||||||
{
|
|
||||||
Id = 20,
|
|
||||||
Name = "Mike"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public IActionResult CreateEmployee([FromBody]Employee employee)
|
|
||||||
{
|
|
||||||
if(!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ObjectResult(employee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Employee
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// 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.IO;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
|
namespace FormatterWebSite
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.UseKestrel()
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -34,18 +32,6 @@ namespace FormatterWebSite
|
||||||
defaults: new { controller = "Home", action = "Index" });
|
defaults: new { controller = "Home", action = "Index" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
var host = new WebHostBuilder()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseStartup<Startup>()
|
|
||||||
.UseKestrel()
|
|
||||||
.UseIISIntegration()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
host.Run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FormatterWebSite
|
||||||
|
{
|
||||||
|
public class StartupWithRespectBrowserAcceptHeader
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddMvc(options =>
|
||||||
|
{
|
||||||
|
options.RespectBrowserAcceptHeader = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseCultureReplacer();
|
||||||
|
|
||||||
|
app.UseMvc(routes =>
|
||||||
|
{
|
||||||
|
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||||
|
defaults: new { controller = "Home", action = "Index" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue