Fix #1370 - Always use the provided formatter in JsonResult
The change here is to always use the provided formatter, instead of using it as a fallback. This is much less surprising for users. There are some other subtle changes here and cleanup of the tests, as well as documentation additions. The primary change is that we still want to run 'select' on a formatter even if it's the only one. This allows us to choose a content type based on the accept header. In the case of a user-provided formatter, we'll try to honor the best possible combination of Accept and specified ContentTypes (specified ContentTypes win if there's a conflict). If nothing works, we'll still run the user-provided formatter and let it decide what to do. In the case of the default (formatters from options) we do conneg, and if there's a conflict, fall back to a global (from services) JsonOutputFormatter - we let it decide what to do. This should leave us with a defined and tested behavior for all cases.
This commit is contained in:
parent
d178200795
commit
105c99cbf2
|
|
@ -9,104 +9,124 @@ using Microsoft.Framework.DependencyInjection;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An action result which formats the given object as JSON.
|
||||
/// </summary>
|
||||
public class JsonResult : ActionResult
|
||||
{
|
||||
private static readonly IList<MediaTypeHeaderValue> _defaultSupportedContentTypes =
|
||||
new List<MediaTypeHeaderValue>()
|
||||
{
|
||||
MediaTypeHeaderValue.Parse("application/json"),
|
||||
MediaTypeHeaderValue.Parse("text/json"),
|
||||
};
|
||||
private IOutputFormatter _defaultFormatter;
|
||||
/// <summary>
|
||||
/// The list of content-types used for formatting when <see cref="ContentTypes"/> is null or empty.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<MediaTypeHeaderValue> DefaultContentTypes = new MediaTypeHeaderValue[]
|
||||
{
|
||||
MediaTypeHeaderValue.Parse("application/json"),
|
||||
MediaTypeHeaderValue.Parse("text/json"),
|
||||
};
|
||||
|
||||
private ObjectResult _objectResult;
|
||||
|
||||
public JsonResult(object data) :
|
||||
this(data, null)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="JsonResult"/> with the given <paramref name="data"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format as JSON.</param>
|
||||
public JsonResult(object value)
|
||||
: this(value, formatter: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="JsonResult"/> class.
|
||||
/// Creates a new <see cref="JsonResult"/> with the given <paramref name="data"/>.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="defaultFormatter">If no matching formatter is found,
|
||||
/// the response is written to using defaultFormatter.</param>
|
||||
/// <remarks>
|
||||
/// The default formatter must be able to handle either application/json
|
||||
/// or text/json.
|
||||
/// </remarks>
|
||||
public JsonResult(object data, IOutputFormatter defaultFormatter)
|
||||
/// <param name="value">The value to format as JSON.</param>
|
||||
/// <param name="formatter">The formatter to use, or <c>null</c> to choose a formatter dynamically.</param>
|
||||
public JsonResult(object value, IOutputFormatter formatter)
|
||||
{
|
||||
_defaultFormatter = defaultFormatter;
|
||||
_objectResult = new ObjectResult(data);
|
||||
Value = value;
|
||||
Formatter = formatter;
|
||||
|
||||
ContentTypes = new List<MediaTypeHeaderValue>();
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _objectResult.Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_objectResult.Value = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the list of supported Content-Types.
|
||||
/// </summary>
|
||||
public IList<MediaTypeHeaderValue> ContentTypes { get; set; }
|
||||
|
||||
public IList<MediaTypeHeaderValue> ContentTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _objectResult.ContentTypes;
|
||||
}
|
||||
set
|
||||
{
|
||||
_objectResult.ContentTypes = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the formatter.
|
||||
/// </summary>
|
||||
public IOutputFormatter Formatter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value to be formatted.
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
|
||||
{
|
||||
var objectResult = new ObjectResult(Value);
|
||||
|
||||
// Set the content type explicitly to application/json and text/json.
|
||||
// if the user has not already set it.
|
||||
if (ContentTypes == null || ContentTypes.Count == 0)
|
||||
{
|
||||
ContentTypes = _defaultSupportedContentTypes;
|
||||
foreach (var contentType in DefaultContentTypes)
|
||||
{
|
||||
objectResult.ContentTypes.Add(contentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objectResult.ContentTypes = ContentTypes;
|
||||
}
|
||||
|
||||
var formatterContext = new OutputFormatterContext()
|
||||
{
|
||||
DeclaredType = _objectResult.DeclaredType,
|
||||
ActionContext = context,
|
||||
DeclaredType = objectResult.DeclaredType,
|
||||
Object = Value,
|
||||
};
|
||||
|
||||
// Need to call this instead of directly calling _objectResult.ExecuteResultAsync
|
||||
// as that sets the status to 406 if a formatter is not found.
|
||||
// this can be cleaned up after https://github.com/aspnet/Mvc/issues/941 gets resolved.
|
||||
var formatter = SelectFormatter(formatterContext);
|
||||
var formatter = SelectFormatter(objectResult, formatterContext);
|
||||
await formatter.WriteAsync(formatterContext);
|
||||
}
|
||||
|
||||
private IOutputFormatter SelectFormatter(OutputFormatterContext formatterContext)
|
||||
private IOutputFormatter SelectFormatter(ObjectResult objectResult, OutputFormatterContext formatterContext)
|
||||
{
|
||||
var defaultFormatters = formatterContext.ActionContext
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<IOutputFormattersProvider>()
|
||||
.OutputFormatters;
|
||||
|
||||
var formatter = _objectResult.SelectFormatter(formatterContext, defaultFormatters);
|
||||
if (formatter == null)
|
||||
if (Formatter == null)
|
||||
{
|
||||
formatter = _defaultFormatter ?? formatterContext.ActionContext
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<JsonOutputFormatter>();
|
||||
}
|
||||
// If no formatter was provided, then run Conneg with the formatters configured in options.
|
||||
var formatters = formatterContext
|
||||
.ActionContext
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<IOutputFormattersProvider>()
|
||||
.OutputFormatters
|
||||
.OfType<IJsonOutputFormatter>()
|
||||
.ToArray();
|
||||
|
||||
return formatter;
|
||||
var formatter = objectResult.SelectFormatter(formatterContext, formatters);
|
||||
if (formatter == null)
|
||||
{
|
||||
// If the available user-configured formatters can't write this type, then fall back to the
|
||||
// 'global' one.
|
||||
formatter = formatterContext
|
||||
.ActionContext
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<JsonOutputFormatter>();
|
||||
|
||||
// Run SelectFormatter again to try to choose a content type that this formatter can do.
|
||||
objectResult.SelectFormatter(formatterContext, new[] { formatter });
|
||||
}
|
||||
|
||||
return formatter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Run SelectFormatter to try to choose a content type that this formatter can do.
|
||||
objectResult.SelectFormatter(formatterContext, new[] { Formatter });
|
||||
return Formatter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An output formatter that specializes in writing JSON content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="JsonResult"/> class filter the collection of
|
||||
/// <see cref="IOutputFormattersProvider.OutputFormatters"/> and use only those which implement
|
||||
/// <see cref="IJsonOutputFormatter"/>.
|
||||
///
|
||||
/// To create a custom formatter that can be used by <see cref="JsonResult"/>, derive from
|
||||
/// <see cref="JsonOutputFormatter"/> or implement <see cref="IJsonOutputFormatter"/>.
|
||||
/// </remarks>
|
||||
public interface IJsonOutputFormatter : IOutputFormatter
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonOutputFormatter : OutputFormatter
|
||||
public class JsonOutputFormatter : OutputFormatter, IJsonOutputFormatter
|
||||
{
|
||||
private JsonSerializerSettings _serializerSettings;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonResultTest
|
||||
|
|
@ -22,114 +22,192 @@ namespace Microsoft.AspNet.Mvc
|
|||
= new byte[] { 123, 34, 102, 111, 111, 34, 58, 34, 97, 98, 99, 100, 34, 125 };
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResult_GeneratesResultsWithoutBOMByDefault()
|
||||
public async Task ExecuteResultAsync_OptionsFormatter_WithoutBOM()
|
||||
{
|
||||
// Arrange
|
||||
var expected = _abcdUTF8Bytes;
|
||||
var memoryStream = new MemoryStream();
|
||||
var response = new Mock<HttpResponse>();
|
||||
response.SetupGet(r => r.Body)
|
||||
.Returns(memoryStream);
|
||||
var context = GetHttpContext(response.Object);
|
||||
var actionContext = new ActionContext(context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
|
||||
var optionsFormatters = new List<IOutputFormatter>()
|
||||
{
|
||||
new XmlDataContractSerializerOutputFormatter(), // This won't be used
|
||||
new JsonOutputFormatter(),
|
||||
};
|
||||
|
||||
var context = GetHttpContext(optionsFormatters);
|
||||
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, memoryStream.ToArray());
|
||||
Assert.Equal(expected, written);
|
||||
Assert.Equal("application/json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResult_IfNoMatchFoundUsesPassedInFormatter()
|
||||
{
|
||||
// Arrange
|
||||
var expected = Enumerable.Concat(Encoding.UTF8.GetPreamble(), _abcdUTF8Bytes);
|
||||
var memoryStream = new MemoryStream();
|
||||
var response = new Mock<HttpResponse>();
|
||||
response.SetupGet(r => r.Body)
|
||||
.Returns(memoryStream);
|
||||
var context = GetHttpContext(response.Object, registerDefaultFormatter: false);
|
||||
var actionContext = new ActionContext(context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var testFormatter = new TestJsonFormatter()
|
||||
{
|
||||
Encoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
var result = new JsonResult(new { foo = "abcd" }, testFormatter);
|
||||
|
||||
// Act
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, memoryStream.ToArray());
|
||||
}
|
||||
|
||||
public async Task ExecuteResult_UsesDefaultFormatter_IfNoneIsRegistered_AndNoneIsPassed()
|
||||
public async Task ExecuteResultAsync_Null()
|
||||
{
|
||||
// Arrange
|
||||
var expected = _abcdUTF8Bytes;
|
||||
var memoryStream = new MemoryStream();
|
||||
var response = new Mock<HttpResponse>();
|
||||
response.SetupGet(r => r.Body)
|
||||
.Returns(memoryStream);
|
||||
var context = GetHttpContext(response.Object, registerDefaultFormatter: false);
|
||||
var actionContext = new ActionContext(context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
|
||||
var optionsFormatters = new List<IOutputFormatter>()
|
||||
{
|
||||
new XmlDataContractSerializerOutputFormatter(), // This won't be used
|
||||
new JsonOutputFormatter(),
|
||||
};
|
||||
|
||||
var context = GetHttpContext(optionsFormatters);
|
||||
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, memoryStream.ToArray());
|
||||
Assert.Equal(expected, written);
|
||||
Assert.Equal("application/json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(HttpResponse response, bool registerDefaultFormatter = true)
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_OptionsFormatter_Conneg()
|
||||
{
|
||||
var defaultFormatters = registerDefaultFormatter ? new List<IOutputFormatter>() { new JsonOutputFormatter() } :
|
||||
new List<IOutputFormatter>();
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
// Arrange
|
||||
var expected = _abcdUTF8Bytes;
|
||||
|
||||
var optionsFormatters = new List<IOutputFormatter>()
|
||||
{
|
||||
new XmlDataContractSerializerOutputFormatter(), // This won't be used
|
||||
new JsonOutputFormatter(),
|
||||
};
|
||||
|
||||
var context = GetHttpContext(optionsFormatters);
|
||||
context.Request.Accept = "text/json";
|
||||
|
||||
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("text/json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesPassedInFormatter()
|
||||
{
|
||||
// Arrange
|
||||
var expected = Enumerable.Concat(Encoding.UTF8.GetPreamble(), _abcdUTF8Bytes);
|
||||
|
||||
var context = GetHttpContext();
|
||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var formatter = new JsonOutputFormatter();
|
||||
formatter.SupportedEncodings.Clear();
|
||||
|
||||
// This is UTF-8 WITH BOM
|
||||
formatter.SupportedEncodings.Add(Encoding.UTF8);
|
||||
|
||||
var result = new JsonResult(new { foo = "abcd" }, formatter);
|
||||
|
||||
// Act
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
var written = GetWrittenBytes(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, written);
|
||||
Assert.Equal("application/json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesPassedInFormatter_ContentTypeSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var expected = _abcdUTF8Bytes;
|
||||
|
||||
var context = GetHttpContext();
|
||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var formatter = new JsonOutputFormatter();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/hal+json"));
|
||||
|
||||
var result = new JsonResult(new { foo = "abcd" }, formatter);
|
||||
result.ContentTypes.Add(MediaTypeHeaderValue.Parse("application/hal+json"));
|
||||
|
||||
// Act
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
var written = GetWrittenBytes(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, written);
|
||||
Assert.Equal("application/hal+json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
// If no formatter in options can match the given content-types, then use the one registered
|
||||
// in services
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesGlobalFormatter_IfNoFormatterIsConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var expected = _abcdUTF8Bytes;
|
||||
|
||||
var context = GetHttpContext(enableFallback: true);
|
||||
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("application/json;charset=utf-8", context.Response.ContentType);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(
|
||||
IReadOnlyList<IOutputFormatter> optionsFormatters = null,
|
||||
bool enableFallback = false)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
|
||||
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
|
||||
httpContext.RequestServices = services.Object;
|
||||
|
||||
var mockFormattersProvider = new Mock<IOutputFormattersProvider>();
|
||||
mockFormattersProvider.SetupGet(o => o.OutputFormatters)
|
||||
.Returns(defaultFormatters);
|
||||
httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider)))
|
||||
.Returns(mockFormattersProvider.Object);
|
||||
httpContext.SetupGet(o => o.Request.Accept)
|
||||
.Returns("");
|
||||
httpContext.SetupGet(c => c.Response).Returns(response);
|
||||
return httpContext.Object;
|
||||
mockFormattersProvider
|
||||
.SetupGet(o => o.OutputFormatters)
|
||||
.Returns(optionsFormatters ?? new List<IOutputFormatter>());
|
||||
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IOutputFormattersProvider)))
|
||||
.Returns(mockFormattersProvider.Object);
|
||||
|
||||
// This is the ultimate fallback, it will be used if none of the formatters from options
|
||||
// work.
|
||||
if (enableFallback)
|
||||
{
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(JsonOutputFormatter)))
|
||||
.Returns(new JsonOutputFormatter());
|
||||
}
|
||||
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
private class TestJsonFormatter : IOutputFormatter
|
||||
private byte[] GetWrittenBytes(HttpContext context)
|
||||
{
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes(Type declaredType,
|
||||
Type runtimeType,
|
||||
MediaTypeHeaderValue contentType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(OutputFormatterContext context)
|
||||
{
|
||||
// Override using the selected encoding.
|
||||
context.SelectedEncoding = Encoding;
|
||||
var jsonFormatter = new JsonOutputFormatter();
|
||||
await jsonFormatter.WriteResponseBodyAsync(context);
|
||||
}
|
||||
context.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
return Assert.IsType<MemoryStream>(context.Response.Body).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class JsonResultTest
|
||||
{
|
||||
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(BasicWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new BasicWebSite.Startup().Configure;
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json")]
|
||||
[InlineData("text/json")]
|
||||
public async Task JsonResult_Conneg(string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/Plain";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.TryAddWithoutValidation("Accept", mediaType);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("{\"Message\":\"hello\"}", content);
|
||||
}
|
||||
|
||||
// Using an Accept header can't force Json to not be Json. If your accept header doesn't jive with the
|
||||
// formatters/content-type configured on the result it will be ignored.
|
||||
[Theory]
|
||||
[InlineData("application/xml")]
|
||||
[InlineData("text/xml")]
|
||||
public async Task JsonResult_Conneg_Fails(string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/Plain";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.TryAddWithoutValidation("Accept", mediaType);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("{\"Message\":\"hello\"}", content);
|
||||
}
|
||||
|
||||
// If the object is null, it will get formatted as JSON. NOT as a 204/NoContent
|
||||
[Fact]
|
||||
public async Task JsonResult_Null()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/Null";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("null", content);
|
||||
}
|
||||
|
||||
// If the object is a string, it will get formatted as JSON. NOT as text/plain.
|
||||
[Fact]
|
||||
public async Task JsonResult_String()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/String";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("\"hello\"", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json")]
|
||||
[InlineData("text/json")]
|
||||
public async Task JsonResult_CustomFormatter_Conneg(string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/CustomFormatter";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.TryAddWithoutValidation("Accept", mediaType);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("{\"message\":\"hello\"}", content);
|
||||
}
|
||||
|
||||
// Using an Accept header can't force Json to not be Json. If your accept header doesn't jive with the
|
||||
// formatters/content-type configured on the result it will be ignored.
|
||||
[Theory]
|
||||
[InlineData("application/xml")]
|
||||
[InlineData("text/xml")]
|
||||
public async Task JsonResult_CustomFormatter_Conneg_Fails(string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/CustomFormatter";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.TryAddWithoutValidation("Accept", mediaType);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("{\"message\":\"hello\"}", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JsonResult_CustomContentType()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/JsonResult/CustomContentType";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/message+json", response.Content.Headers.ContentType.MediaType);
|
||||
Assert.Equal("{\"Message\":\"hello\"}", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BasicWebSite.Controllers
|
||||
{
|
||||
public class JsonResultController : Controller
|
||||
{
|
||||
public JsonResult Plain()
|
||||
{
|
||||
return Json(new { Message = "hello" });
|
||||
}
|
||||
|
||||
public JsonResult CustomFormatter()
|
||||
{
|
||||
var formatter = new JsonOutputFormatter();
|
||||
formatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
|
||||
return new JsonResult(new { Message = "hello" }, formatter);
|
||||
}
|
||||
|
||||
public JsonResult CustomContentType()
|
||||
{
|
||||
var formatter = new JsonOutputFormatter();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/message+json"));
|
||||
|
||||
var result = new JsonResult(new { Message = "hello" }, formatter);
|
||||
result.ContentTypes.Add(MediaTypeHeaderValue.Parse("application/message+json"));
|
||||
return result;
|
||||
}
|
||||
|
||||
public JsonResult Null()
|
||||
{
|
||||
return Json(null);
|
||||
}
|
||||
|
||||
public JsonResult String()
|
||||
{
|
||||
return Json("hello");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue