Adding OutputFormatter base class
This commit is contained in:
parent
e28adbfb3d
commit
1df4738a19
|
|
@ -8,9 +8,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
internal static class Encodings
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns UTF8 Encoding without BOM and throws on invalid bytes
|
||||
/// Returns UTF8 Encoding without BOM and throws on invalid bytes.
|
||||
/// </summary>
|
||||
public static readonly Encoding UTF8EncodingWithoutBOM
|
||||
= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
|
||||
/// </summary>
|
||||
public static readonly Encoding UnicodeEncodingWithBOM = new UnicodeEncoding(bigEndian: false,
|
||||
byteOrderMark: true,
|
||||
throwOnInvalidBytes: true);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class ObjectContentResult : ActionResult
|
||||
public class ObjectResult : ActionResult
|
||||
{
|
||||
public object Value { get; set; }
|
||||
|
||||
public ObjectContentResult(object value)
|
||||
public ObjectResult(object value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonOutputFormatter : OutputFormatter
|
||||
{
|
||||
private readonly JsonSerializerSettings _settings;
|
||||
private readonly bool _indent;
|
||||
|
||||
public JsonOutputFormatter([NotNull] JsonSerializerSettings settings, bool indent)
|
||||
{
|
||||
_settings = settings;
|
||||
_indent = indent;
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UnicodeEncodingWithBOM);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
|
||||
}
|
||||
|
||||
public static JsonSerializerSettings CreateDefaultSettings()
|
||||
{
|
||||
return new JsonSerializerSettings()
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types.
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
}
|
||||
|
||||
public void WriteObject([NotNull] TextWriter writer, object value)
|
||||
{
|
||||
using (var jsonWriter = CreateJsonWriter(writer))
|
||||
{
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
jsonSerializer.Serialize(jsonWriter, value);
|
||||
|
||||
// We're explicitly calling flush here to simplify the debugging experience because the
|
||||
// underlying TextWriter might be long-lived. If this method ends up being called repeatedly
|
||||
// for a request, we should revisit.
|
||||
jsonWriter.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private JsonWriter CreateJsonWriter(TextWriter writer)
|
||||
{
|
||||
var jsonWriter = new JsonTextWriter(writer);
|
||||
if (_indent)
|
||||
{
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
}
|
||||
|
||||
jsonWriter.CloseOutput = false;
|
||||
|
||||
return jsonWriter;
|
||||
}
|
||||
|
||||
private JsonSerializer CreateJsonSerializer()
|
||||
{
|
||||
var jsonSerializer = JsonSerializer.Create(_settings);
|
||||
return jsonSerializer;
|
||||
}
|
||||
|
||||
public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
|
||||
{
|
||||
return SupportedMediaTypes.Any(supportedMediaType =>
|
||||
contentType.RawValue.Equals(supportedMediaType.RawValue,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public override Task WriteAsync(OutputFormatterContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var response = context.HttpContext.Response;
|
||||
|
||||
// The content type including the encoding should have been set already.
|
||||
// In case it was not present, a default will be selected.
|
||||
var selectedEncoding = SelectCharacterEncoding(MediaTypeHeaderValue.Parse(response.ContentType));
|
||||
using (var writer = new StreamWriter(response.Body, selectedEncoding))
|
||||
{
|
||||
using (var jsonWriter = CreateJsonWriter(writer))
|
||||
{
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
jsonSerializer.Serialize(jsonWriter, context.ObjectResult.Value);
|
||||
|
||||
// We're explicitly calling flush here to simplify the debugging experience because the
|
||||
// underlying TextWriter might be long-lived. If this method ends up being called repeatedly
|
||||
// for a request, we should revisit.
|
||||
jsonWriter.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes an object to the output stream.
|
||||
/// </summary>
|
||||
public abstract class OutputFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the mutable collection of character encodings supported by
|
||||
/// this <see cref="OutputFormatter"/> instance. The encodings are
|
||||
/// used when writing the data.
|
||||
/// </summary>
|
||||
public List<Encoding> SupportedEncodings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by
|
||||
/// this <see cref="OutputFormatter"/> instance.
|
||||
/// </summary>
|
||||
public List<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
|
||||
/// </summary>
|
||||
protected OutputFormatter()
|
||||
{
|
||||
SupportedEncodings = new List<Encoding>();
|
||||
SupportedMediaTypes = new List<MediaTypeHeaderValue>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the best <see cref="Encoding"/> amongst the supported encodings
|
||||
/// for reading or writing an HTTP entity body based on the provided <paramref name="contentTypeHeader"/>.
|
||||
/// </summary>
|
||||
/// <param name="contentTypeHeader">The content type header provided as part of the request or response.</param>
|
||||
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
|
||||
public virtual Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentTypeHeader)
|
||||
{
|
||||
Encoding encoding = null;
|
||||
if (contentTypeHeader != null)
|
||||
{
|
||||
// Find encoding based on content type charset parameter
|
||||
var charset = contentTypeHeader.Charset;
|
||||
if (!String.IsNullOrWhiteSpace(charset))
|
||||
{
|
||||
encoding = SupportedEncodings.FirstOrDefault(
|
||||
supportedEncoding =>
|
||||
charset.Equals(supportedEncoding.WebName,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
// We didn't find a character encoding match based on the content headers.
|
||||
// Instead we try getting the default character encoding.
|
||||
if (SupportedEncodings.Count > 0)
|
||||
{
|
||||
encoding = SupportedEncodings[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
// No supported encoding was found so there is no way for us to start writing.
|
||||
throw new InvalidOperationException(Resources.FormatOutputFormatterNoEncoding(GetType().FullName));
|
||||
}
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this <see cref="OutputFormatter"/> can serialize
|
||||
/// an object of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="context">The formatter context associated with the call</param>
|
||||
/// <param name="contentType">The desired contentType on the response.</param>
|
||||
/// <returns>True if this <see cref="OutputFormatter"/> is able to serialize the object
|
||||
/// represent by <paramref name="context"/>'s ObjectResult and supports the passed in
|
||||
/// <paramref name="contentType"/>.
|
||||
/// False otherwise.</returns>
|
||||
public abstract bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType);
|
||||
|
||||
/// <summary>
|
||||
/// Writes given <paramref name="value"/> to the HttpResponse <paramref name="response"/> body stream.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
||||
/// <returns>A Task that serializes the value to the <paramref name="context"/>'s response message.</returns>
|
||||
public abstract Task WriteAsync(OutputFormatterContext context, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class OutputFormatterContext
|
||||
{
|
||||
public ObjectResult ObjectResult { get; set; }
|
||||
|
||||
public Type DeclaredType { get; set; }
|
||||
|
||||
public HttpContext HttpContext { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// 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.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonOutputFormatter
|
||||
{
|
||||
private readonly JsonSerializerSettings _settings;
|
||||
private readonly bool _indent;
|
||||
|
||||
public JsonOutputFormatter([NotNull] JsonSerializerSettings settings, bool indent)
|
||||
{
|
||||
_settings = settings;
|
||||
_indent = indent;
|
||||
}
|
||||
|
||||
public static JsonSerializerSettings CreateDefaultSettings()
|
||||
{
|
||||
return new JsonSerializerSettings()
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types.
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
}
|
||||
|
||||
public void WriteObject([NotNull] TextWriter writer, object value)
|
||||
{
|
||||
using (var jsonWriter = CreateJsonWriter(writer))
|
||||
{
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
jsonSerializer.Serialize(jsonWriter, value);
|
||||
|
||||
// We're explicitly calling flush here to simplify the debugging experience because the
|
||||
// underlying TextWriter might be long-lived. If this method ends up being called repeatedly
|
||||
// for a request, we should revisit.
|
||||
jsonWriter.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private JsonWriter CreateJsonWriter([NotNull] TextWriter writer)
|
||||
{
|
||||
var jsonWriter = new JsonTextWriter(writer);
|
||||
if (_indent)
|
||||
{
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
}
|
||||
|
||||
jsonWriter.CloseOutput = false;
|
||||
|
||||
return jsonWriter;
|
||||
}
|
||||
|
||||
private JsonSerializer CreateJsonSerializer()
|
||||
{
|
||||
var jsonSerializer = JsonSerializer.Create(_settings);
|
||||
return jsonSerializer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,9 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="project.json" />
|
||||
<Content Include="Resources.resx" />
|
||||
<Content Include="Resources.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AcceptVerbsAttribute.cs" />
|
||||
|
|
@ -30,6 +32,9 @@
|
|||
<Compile Include="ExpiringFileInfoCache.cs" />
|
||||
<Compile Include="Extensions\ViewEngineDescriptorExtensions.cs" />
|
||||
<Compile Include="IExpiringFileInfoCache.cs" />
|
||||
<Compile Include="Formatters\OutputFormatterContext.cs" />
|
||||
<Compile Include="Formatters\JsonOutputFormatter.cs" />
|
||||
<Compile Include="Formatters\OutputFormatter.cs" />
|
||||
<Compile Include="ReflectedActionDescriptor.cs" />
|
||||
<Compile Include="ReflectedActionDescriptorProvider.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\IReflectedApplicationModelConvention.cs" />
|
||||
|
|
@ -47,7 +52,7 @@
|
|||
<Compile Include="ActionResults\HttpStatusCodeResult.cs" />
|
||||
<Compile Include="ActionResults\JsonResult.cs" />
|
||||
<Compile Include="ActionResults\NoContentResult.cs" />
|
||||
<Compile Include="ActionResults\ObjectContentResult.cs" />
|
||||
<Compile Include="ActionResults\ObjectResult.cs" />
|
||||
<Compile Include="ActionResults\RedirectResult.cs" />
|
||||
<Compile Include="ActionResults\RedirectToActionResult.cs" />
|
||||
<Compile Include="ActionResults\RedirectToRouteResult.cs" />
|
||||
|
|
@ -142,7 +147,6 @@
|
|||
<Compile Include="Injector.cs" />
|
||||
<Compile Include="Internal\TypeHelper.cs" />
|
||||
<Compile Include="IUrlHelper.cs" />
|
||||
<Compile Include="JsonOutputFormatter.cs" />
|
||||
<Compile Include="MvcOptions.cs" />
|
||||
<Compile Include="MvcRouteHandler.cs" />
|
||||
<Compile Include="NonActionAttribute.cs" />
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("TypeMustDeriveFromType"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.
|
||||
/// </summary>
|
||||
internal static string OutputFormatterNoEncoding
|
||||
{
|
||||
get { return GetString("OutputFormatterNoEncoding"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.
|
||||
/// </summary>
|
||||
internal static string FormatOutputFormatterNoEncoding(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormatterNoEncoding"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(actualReturnType));
|
||||
}
|
||||
|
||||
return new ObjectContentResult(actionReturnValue);
|
||||
return new ObjectResult(actionReturnValue);
|
||||
}
|
||||
|
||||
private IFilter[] GetFilters()
|
||||
|
|
|
|||
|
|
@ -312,4 +312,7 @@
|
|||
<data name="TypeMustDeriveFromType" xml:space="preserve">
|
||||
<value>The type '{0}' must derive from '{1}'.</value>
|
||||
</data>
|
||||
<data name="OutputFormatterNoEncoding" xml:space="preserve">
|
||||
<value>No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.FileSystems": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Common": "",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding": "",
|
||||
"Microsoft.AspNet.Routing": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
var actionContext = CreateMockActionContext();
|
||||
|
||||
// Act
|
||||
var result = new ObjectContentResult(input);
|
||||
var result = new ObjectResult(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(input, result.Value);
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
var actionContext = CreateMockActionContext(httpResponse.Object);
|
||||
|
||||
// Act
|
||||
var result = new ObjectContentResult(input);
|
||||
var result = new ObjectResult(input);
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
|
||||
// Assert
|
||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
}
|
||||
|
||||
// Act
|
||||
var result = new ObjectContentResult(nonStringValue);
|
||||
var result = new ObjectResult(nonStringValue);
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Test
|
||||
{
|
||||
public class OutputFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void SelectCharacterEncoding_FormatterWithNoEncoding_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var testFormatter = new TestFormatter();
|
||||
var testContentType = MediaTypeHeaderValue.Parse("text/invalid");
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => testFormatter.SelectCharacterEncoding(testContentType));
|
||||
Assert.Equal("No encoding found for output formatter "+
|
||||
"'Microsoft.AspNet.Mvc.Test.OutputFormatterTests+TestFormatter'." +
|
||||
" There must be at least one supported encoding registered in order for the" +
|
||||
" output formatter to write content.", ex.Message);
|
||||
}
|
||||
|
||||
private class TestFormatter : OutputFormatter
|
||||
{
|
||||
public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task WriteAsync(OutputFormatterContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
<Compile Include="ExpiringFileInfoCacheTest.cs" />
|
||||
<Compile Include="DefaultActionDiscoveryConventionsTests.cs" />
|
||||
<Compile Include="Extensions\ViewEngineDscriptorExtensionsTest.cs" />
|
||||
<Compile Include="Formatters\OutputFormatterTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedParameterModelTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedActionModelTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedControllerModelTests.cs" />
|
||||
|
|
|
|||
|
|
@ -1261,7 +1261,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var actualResult = ReflectedActionInvoker.CreateActionResult(type, input);
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectContentResult>(actualResult);
|
||||
var contentResult = Assert.IsType<ObjectResult>(actualResult);
|
||||
Assert.Same(input, contentResult.Value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc" : "",
|
||||
"Microsoft.AspNet.Mvc.Core" : "",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding": "",
|
||||
|
|
|
|||
Loading…
Reference in New Issue