Skip to content
This repository has been archived by the owner on May 16, 2022. It is now read-only.

Implemented some improvements mentioned in #97 #112

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 27 additions & 44 deletions src/Utf8Json.AspNetCoreMvcFormatter/Formatter.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,43 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using System.Text;
using Microsoft.AspNetCore.Mvc.Formatters;
using System.Threading.Tasks;

namespace Utf8Json.AspNetCoreMvcFormatter
{
public class JsonOutputFormatter : IOutputFormatter //, IApiResponseTypeMetadataProvider
public class JsonOutputFormatter : TextOutputFormatter
{
const string ContentType = "application/json";
static readonly string[] SupportedContentTypes = new[] { ContentType };

readonly IJsonFormatterResolver resolver;

public JsonOutputFormatter()
: this(null)
{

}

public JsonOutputFormatter(IJsonFormatterResolver resolver)
{
this.resolver = resolver ?? JsonSerializer.DefaultResolver;
SupportedEncodings.Add(Encoding.UTF8);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
}

//public IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type objectType)
//{
// return SupportedContentTypes;
//}

public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
return true;
}

public Task WriteAsync(OutputFormatterWriteContext context)
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
context.HttpContext.Response.ContentType = ContentType;

// when 'object' use the concrete type(object.GetType())
if (context.ObjectType == typeof(object))
{
return JsonSerializer.NonGeneric.SerializeAsync(context.HttpContext.Response.Body, context.Object, resolver);
}
else
{
return JsonSerializer.NonGeneric.SerializeAsync(context.ObjectType, context.HttpContext.Response.Body, context.Object, resolver);
}
return context.ObjectType == typeof(object)
? JsonSerializer.NonGeneric.SerializeAsync(context.HttpContext.Response.Body,
context.Object,
resolver)
: JsonSerializer.NonGeneric.SerializeAsync(context.ObjectType,
context.HttpContext.Response.Body,
context.Object,
resolver);
}
}

public class JsonInputFormatter : IInputFormatter // , IApiRequestFormatMetadataProvider
public class JsonInputFormatter : TextInputFormatter
{
const string ContentType = "application/json";
static readonly string[] SupportedContentTypes = new[] { ContentType };

readonly IJsonFormatterResolver resolver;

public JsonInputFormatter()
Expand All @@ -62,23 +49,19 @@ public JsonInputFormatter()
public JsonInputFormatter(IJsonFormatterResolver resolver)
{
this.resolver = resolver ?? JsonSerializer.DefaultResolver;
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
}

//public IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type objectType)
//{
// return SupportedContentTypes;
//}

public bool CanRead(InputFormatterContext context)
{
return true;
}

public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var request = context.HttpContext.Request;
var result = JsonSerializer.NonGeneric.Deserialize(context.ModelType, request.Body, resolver);
return InputFormatterResult.SuccessAsync(result);
var result = await JsonSerializer.NonGeneric.DeserializeAsync(context.ModelType,
request.Body,
resolver).ConfigureAwait(false);
return InputFormatterResult.Success(result);
}
}
}
12 changes: 12 additions & 0 deletions src/Utf8Json.AspNetCoreMvcFormatter/MediaTypeHeaderValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Net.Http.Headers;

namespace Utf8Json.AspNetCoreMvcFormatter
{
internal class MediaTypeHeaderValues
{
public static readonly MediaTypeHeaderValue ApplicationJson = MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly();
public static readonly MediaTypeHeaderValue TextJson = MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly();
public static readonly MediaTypeHeaderValue ApplicationJsonPatch = MediaTypeHeaderValue.Parse("application/json-patch+json").CopyAsReadOnly();
public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax = MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
193 changes: 193 additions & 0 deletions tests/Utf8Json.Tests/AspNetCoreFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;
using Utf8Json.AspNetCoreMvcFormatter;
using Utf8Json.Resolvers;
using Xunit;

namespace Utf8Json.Tests
{
public class AspNetCoreFormatterTests
{
private MemoryStream responseStream = new MemoryStream();
private InputFormatter inputFormatter;
private OutputFormatter outputFormatter;
private HttpContext context;
private TestClass obj = new TestClass
{
A = 2
};

public class TestClass
{
public int A { get; set; }
}

public AspNetCoreFormatterTests()
{
context = new DefaultHttpContext();
context.Response.Body = responseStream;
}

[Fact]
public async Task OutputFormatterShouldFormatWithDefaultParameters()
{
outputFormatter = new JsonOutputFormatter();
var outputContext = CreateOutputFormatterContext(obj, obj.GetType(), "application/json");

Assert.True(outputFormatter.CanWriteResult(outputContext));
await outputFormatter.WriteAsync(outputContext);

Assert.Equal(outputContext.ContentType, "application/json; charset=utf-8");
Assert.Equal(200, outputContext.HttpContext.Response.StatusCode);

var responseText = Encoding.UTF8.GetString(responseStream.ToArray());
Assert.Equal("{\"A\":2}", responseText);
}

[Fact]
public async Task OutputFormatterShouldRespectSerializerSettings()
{
outputFormatter = new JsonOutputFormatter(StandardResolver.CamelCase);
var outputContext = CreateOutputFormatterContext(obj, obj.GetType(), "application/json");

await outputFormatter.WriteAsync(outputContext);

Assert.Equal(outputContext.ContentType, "application/json; charset=utf-8");
Assert.Equal(200, outputContext.HttpContext.Response.StatusCode);

var responseText = Encoding.UTF8.GetString(responseStream.ToArray());
Assert.Equal("{\"a\":2}", responseText);
}

[Fact]
public void OutputFormatterShouldNotWriteResultWithUnsupportedContentType()
{
outputFormatter = new JsonOutputFormatter();
var outputContext = CreateOutputFormatterContext(obj, obj.GetType(), "application/xml");

Assert.False(outputFormatter.CanWriteResult(outputContext));
}

[Theory]
[InlineData("application/json", false, "application/json")]
[InlineData("application/json", true, "application/json")]
[InlineData("application/xml", false, null)]
[InlineData("application/xml", true, null)]
[InlineData("application/*", false, "application/json")]
[InlineData("text/*", false, "text/json")]
[InlineData("custom/*", false, null)]
[InlineData("application/json;v=2", false, null)]
[InlineData("application/json;v=2", true, null)]
[InlineData("application/some.entity+json", false, null)]
[InlineData("application/some.entity+json", true, "application/some.entity+json")]
[InlineData("application/some.entity+json;v=2", true, "application/some.entity+json;v=2")]
[InlineData("application/some.entity+xml", true, null)]
public void OutputFormatterCanWriteReturnsExpectedValue(string mediaType, bool isServerDefined, string expectedResult)
{
var formatter = new JsonOutputFormatter();
var outputFormatterContext = CreateOutputFormatterContext(new object(), typeof(object), mediaType, isServerDefined);

var actualCanWriteValue = formatter.CanWriteResult(outputFormatterContext);

// Assert
var expectedContentType = expectedResult ?? mediaType;
Assert.Equal(expectedResult != null, actualCanWriteValue);
Assert.Equal(new StringSegment(expectedContentType), outputFormatterContext.ContentType);
}

[Fact]
public async Task InputFormatterShouldFormatWithDefaultParameters()
{
inputFormatter = new JsonInputFormatter();
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{\"A\":2}"));
context.Request.ContentLength = context.Request.Body.Length;
context.Request.ContentType = "application/json";

var inputContext = CreateInputFormatterContext(typeof(TestClass));
Assert.True(inputFormatter.CanRead(inputContext));
var inputFormatterResult = await inputFormatter.ReadAsync(inputContext);
Assert.Equal(2, ((TestClass)inputFormatterResult.Model).A);
}

[Fact]
public async Task InputFormatterShouldRespectSerializerSettings()
{
inputFormatter = new JsonInputFormatter(StandardResolver.CamelCase);
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{\"a\":2}"));
context.Request.ContentLength = context.Request.Body.Length;
context.Request.ContentType = "application/json";

var inputContext = CreateInputFormatterContext(typeof(TestClass));
Assert.True(inputFormatter.CanRead(inputContext));
var inputFormatterResult = await inputFormatter.ReadAsync(inputContext);
Assert.Equal(2, ((TestClass)inputFormatterResult.Model).A);
}

[Theory]
[InlineData("application/json", true)]
[InlineData("application/*", false)]
[InlineData("*/*", false)]
[InlineData("text/json", true)]
[InlineData("text/*", false)]
[InlineData("text/xml", false)]
[InlineData("application/xml", false)]
[InlineData("application/some.entity+json", true)]
[InlineData("application/some.entity+json;v=2", true)]
[InlineData("application/some.entity+xml", false)]
[InlineData("application/some.entity+*", false)]
[InlineData("", false)]
[InlineData(null, false)]
[InlineData("invalid", false)]
public void InputFormatterCanReadAnySupportedContentType(string requestContentType, bool expectedCanRead)
{
context.Request.ContentType = requestContentType;
inputFormatter = new JsonInputFormatter();
var formatterContext = CreateInputFormatterContext(typeof(string));

var result = inputFormatter.CanRead(formatterContext);

Assert.Equal(expectedCanRead, result);
}




private OutputFormatterWriteContext CreateOutputFormatterContext(
object outputValue,
Type outputType,
string contentType = "application/xml; charset=utf-8",
bool contentTypeIsServerDefined = false)
{
return new OutputFormatterWriteContext(
context,
(stream, encoding) => new StreamWriter(stream),
outputType,
outputValue)
{
ContentType = new StringSegment(contentType),
ContentTypeIsServerDefined = contentTypeIsServerDefined
};
}

private InputFormatterContext CreateInputFormatterContext(
Type modelType,
string modelName = null)
{
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType);

return new InputFormatterContext(
context,
modelName ?? string.Empty,
new ModelStateDictionary(),
metadata,
(stream, encoding) => new StreamReader(stream));
}
}
}
1 change: 1 addition & 0 deletions tests/Utf8Json.Tests/Utf8Json.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Utf8Json.AspNetCoreMvcFormatter\Utf8Json.AspNetCoreMvcFormatter.csproj" />
<ProjectReference Include="..\..\src\Utf8Json\Utf8Json.csproj" />
</ItemGroup>

Expand Down