Add converters for ProblemDetails and ValidationProblemDetails (#12216)

* Add converters for ProblemDetails and ValidationProblemDetails

ProblemDetails & ValidationProblemDetails require IgnoreNullValues to be
applied on the types. We'll use a converter to workaround the absence of this
feature

Fixes https://github.com/aspnet/AspNetCore/issues/11522
This commit is contained in:
Pranav K 2019-07-22 09:40:04 -07:00 committed by GitHub
parent f8c22859a0
commit a99ab25700
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 545 additions and 35 deletions

View File

@ -960,12 +960,17 @@ namespace Microsoft.AspNetCore.Mvc
public partial class ProblemDetails public partial class ProblemDetails
{ {
public ProblemDetails() { } public ProblemDetails() { }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("detail")]
public string Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[System.Text.Json.Serialization.JsonExtensionDataAttribute] [System.Text.Json.Serialization.JsonExtensionDataAttribute]
public System.Collections.Generic.IDictionary<string, object> Extensions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Collections.Generic.IDictionary<string, object> Extensions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("instance")]
public string Instance { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Instance { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("status")]
public int? Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public int? Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("title")]
public string Title { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Title { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("type")]
public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=true)] [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
@ -1249,6 +1254,7 @@ namespace Microsoft.AspNetCore.Mvc
public ValidationProblemDetails() { } public ValidationProblemDetails() { }
public ValidationProblemDetails(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary modelState) { } public ValidationProblemDetails(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary modelState) { }
public ValidationProblemDetails(System.Collections.Generic.IDictionary<string, string[]> errors) { } public ValidationProblemDetails(System.Collections.Generic.IDictionary<string, string[]> errors) { }
[System.Text.Json.Serialization.JsonPropertyNameAttribute("errors")]
public System.Collections.Generic.IDictionary<string, string[]> Errors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Collections.Generic.IDictionary<string, string[]> Errors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
} }
public partial class VirtualFileResult : Microsoft.AspNetCore.Mvc.FileResult public partial class VirtualFileResult : Microsoft.AspNetCore.Mvc.FileResult

View File

@ -0,0 +1,133 @@
// 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;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
internal class ProblemDetailsJsonConverter : JsonConverter<ProblemDetails>
{
private static readonly JsonEncodedText Type = JsonEncodedText.Encode("type");
private static readonly JsonEncodedText Title = JsonEncodedText.Encode("title");
private static readonly JsonEncodedText Status = JsonEncodedText.Encode("status");
private static readonly JsonEncodedText Detail = JsonEncodedText.Encode("detail");
private static readonly JsonEncodedText Instance = JsonEncodedText.Encode("instance");
public override ProblemDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var problemDetails = new ProblemDetails();
if (!reader.Read())
{
throw new JsonException(Resources.UnexpectedJsonEnd);
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
ReadValue(ref reader, problemDetails, options);
}
if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException(Resources.UnexpectedJsonEnd);
}
return problemDetails;
}
public override void Write(Utf8JsonWriter writer, ProblemDetails value, JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteProblemDetails(writer, value, options);
writer.WriteEndObject();
}
internal static void ReadValue(ref Utf8JsonReader reader, ProblemDetails value, JsonSerializerOptions options)
{
if (TryReadStringProperty(ref reader, Type, out var propertyValue))
{
value.Type = propertyValue;
}
else if (TryReadStringProperty(ref reader, Title, out propertyValue))
{
value.Title = propertyValue;
}
else if (TryReadStringProperty(ref reader, Detail, out propertyValue))
{
value.Detail = propertyValue;
}
else if (TryReadStringProperty(ref reader, Instance, out propertyValue))
{
value.Instance = propertyValue;
}
else if (reader.ValueTextEquals(Status.EncodedUtf8Bytes))
{
reader.Read();
if (reader.TokenType == JsonTokenType.Null)
{
// Nothing to do here.
}
else
{
value.Status = reader.GetInt32();
}
}
else
{
var key = reader.GetString();
reader.Read();
value.Extensions[key] = JsonSerializer.Deserialize(ref reader, typeof(object), options);
}
}
internal static bool TryReadStringProperty(ref Utf8JsonReader reader, JsonEncodedText propertyName, out string value)
{
if (!reader.ValueTextEquals(propertyName.EncodedUtf8Bytes))
{
value = default;
return false;
}
reader.Read();
value = reader.GetString();
return true;
}
internal static void WriteProblemDetails(Utf8JsonWriter writer, ProblemDetails value, JsonSerializerOptions options)
{
if (value.Type != null)
{
writer.WriteString(Type, value.Type);
}
if (value.Title != null)
{
writer.WriteString(Title, value.Title);
}
if (value.Status != null)
{
writer.WriteNumber(Status, value.Status.Value);
}
if (value.Detail != null)
{
writer.WriteString(Detail, value.Detail);
}
if (value.Instance != null)
{
writer.WriteString(Instance, value.Instance);
}
foreach (var kvp in value.Extensions)
{
writer.WritePropertyName(kvp.Key);
JsonSerializer.Serialize(writer, kvp.Value, kvp.Value?.GetType() ?? typeof(object), options);
}
}
}
}

View File

@ -0,0 +1,66 @@
// 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;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.Core;
using static Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsJsonConverter;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
internal class ValidationProblemDetailsJsonConverter : JsonConverter<ValidationProblemDetails>
{
private static readonly JsonEncodedText Errors = JsonEncodedText.Encode("errors");
public override ValidationProblemDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var problemDetails = new ValidationProblemDetails();
if (!reader.Read())
{
throw new JsonException(Resources.UnexpectedJsonEnd);
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
if (reader.ValueTextEquals(Errors.EncodedUtf8Bytes))
{
var errors = JsonSerializer.Deserialize<Dictionary<string, string[]>>(ref reader, options);
foreach (var item in errors)
{
problemDetails.Errors[item.Key] = item.Value;
}
}
else
{
ReadValue(ref reader, problemDetails, options);
}
}
if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException(Resources.UnexpectedJsonEnd);
}
return problemDetails;
}
public override void Write(Utf8JsonWriter writer, ValidationProblemDetails value, JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteProblemDetails(writer, value, options);
writer.WriteStartObject(Errors);
foreach (var kvp in value.Errors)
{
writer.WritePropertyName(kvp.Key);
JsonSerializer.Serialize(writer, kvp.Value, kvp.Value?.GetType() ?? typeof(object), options);
}
writer.WriteEndObject();
writer.WriteEndObject();
}
}
}

View File

@ -4,12 +4,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
/// <summary> /// <summary>
/// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807. /// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
/// </summary> /// </summary>
[JsonConverter(typeof(ProblemDetailsJsonConverter))]
public class ProblemDetails public class ProblemDetails
{ {
/// <summary> /// <summary>
@ -18,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
/// "about:blank". /// "about:blank".
/// </summary> /// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } public string Type { get; set; }
/// <summary> /// <summary>
@ -25,21 +28,25 @@ namespace Microsoft.AspNetCore.Mvc
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation; /// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
/// see[RFC7231], Section 3.4). /// see[RFC7231], Section 3.4).
/// </summary> /// </summary>
[JsonPropertyName("title")]
public string Title { get; set; } public string Title { get; set; }
/// <summary> /// <summary>
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
/// </summary> /// </summary>
[JsonPropertyName("status")]
public int? Status { get; set; } public int? Status { get; set; }
/// <summary> /// <summary>
/// A human-readable explanation specific to this occurrence of the problem. /// A human-readable explanation specific to this occurrence of the problem.
/// </summary> /// </summary>
[JsonPropertyName("detail")]
public string Detail { get; set; } public string Detail { get; set; }
/// <summary> /// <summary>
/// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced. /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
/// </summary> /// </summary>
[JsonPropertyName("instance")]
public string Instance { get; set; } public string Instance { get; set; }
/// <summary> /// <summary>

View File

@ -507,4 +507,7 @@
<data name="ObjectResultExecutor_MaxEnumerationExceeded" xml:space="preserve"> <data name="ObjectResultExecutor_MaxEnumerationExceeded" xml:space="preserve">
<value>'{0}' reached the configured maximum size of the buffer when enumerating a value of type '{1}'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable&lt;&gt;' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting '{1}' into a list rather than increasing the limit.</value> <value>'{0}' reached the configured maximum size of the buffer when enumerating a value of type '{1}'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable&lt;&gt;' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting '{1}' into a list rather than increasing the limit.</value>
</data> </data>
<data name="UnexpectedJsonEnd" xml:space="preserve">
<value>Unexcepted end when reading JSON.</value>
</data>
</root> </root>

View File

@ -3,7 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
@ -11,6 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// A <see cref="ProblemDetails"/> for validation errors. /// A <see cref="ProblemDetails"/> for validation errors.
/// </summary> /// </summary>
[JsonConverter(typeof(ValidationProblemDetailsJsonConverter))]
public class ValidationProblemDetails : ProblemDetails public class ValidationProblemDetails : ProblemDetails
{ {
/// <summary> /// <summary>
@ -83,6 +86,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Gets the validation errors associated with this instance of <see cref="ValidationProblemDetails"/>. /// Gets the validation errors associated with this instance of <see cref="ValidationProblemDetails"/>.
/// </summary> /// </summary>
[JsonPropertyName("errors")]
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal); public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
} }
} }

View File

@ -0,0 +1,147 @@
// 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 System.Text;
using System.Text.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class ProblemDetailsConverterTest
{
private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().JsonSerializerOptions;
[Fact]
public void Read_ThrowsIfJsonIsIncomplete()
{
// Arrange
var json = "{";
var converter = new ProblemDetailsJsonConverter();
// Act & Assert
var ex = Record.Exception(() =>
{
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
});
Assert.IsAssignableFrom<JsonException>(ex);
}
[Fact]
public void Read_Works()
{
// Arrange
var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
var title = "Not found";
var status = 404;
var detail = "Product not found";
var instance = "http://example.com/products/14";
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
var converter = new ProblemDetailsJsonConverter();
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
// Act
var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Equal(instance, problemDetails.Instance);
Assert.Equal(detail, problemDetails.Detail);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
});
}
[Fact]
public void Read_WithSomeMissingValues_Works()
{
// Arrange
var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
var title = "Not found";
var status = 404;
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"}}";
var converter = new ProblemDetailsJsonConverter();
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
// Act
var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
});
}
[Fact]
public void Write_Works()
{
// Arrange
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var value = new ProblemDetails
{
Title = "Not found",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Status = 404,
Detail = "Product not found",
Instance = "http://example.com/products/14",
Extensions =
{
{ "traceId", traceId },
{ "some-data", new[] { "value1", "value2" } }
}
};
var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status},\"detail\":\"{value.Detail}\",\"instance\":\"{JsonEncodedText.Encode(value.Instance)}\",\"traceId\":\"{traceId}\",\"some-data\":[\"value1\",\"value2\"]}}";
var converter = new ProblemDetailsJsonConverter();
var stream = new MemoryStream();
// Act
using (var writer = new Utf8JsonWriter(stream))
{
converter.Write(writer, value, JsonSerializerOptions);
}
// Assert
var actual = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(expected, actual);
}
[Fact]
public void Write_WithSomeMissingContent_Works()
{
// Arrange
var value = new ProblemDetails
{
Title = "Not found",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Status = 404,
};
var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status}}}";
var converter = new ProblemDetailsJsonConverter();
var stream = new MemoryStream();
// Act
using (var writer = new Utf8JsonWriter(stream))
{
converter.Write(writer, value, JsonSerializerOptions);
}
// Assert
var actual = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,145 @@
// 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 System.Linq;
using System.Text;
using System.Text.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class ValidationProblemDetailsConverterTest
{
private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().JsonSerializerOptions;
[Fact]
public void Read_Works()
{
// Arrange
var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
var title = "Not found";
var status = 404;
var detail = "Product not found";
var instance = "http://example.com/products/14";
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"," +
"\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
var converter = new ValidationProblemDetailsJsonConverter();
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
// Act
var problemDetails = converter.Read(ref reader, typeof(ValidationProblemDetails), JsonSerializerOptions);
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Equal(instance, problemDetails.Instance);
Assert.Equal(detail, problemDetails.Detail);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
});
Assert.Collection(
problemDetails.Errors.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("key0", kvp.Key);
Assert.Equal(new[] { "error0" }, kvp.Value);
},
kvp =>
{
Assert.Equal("key1", kvp.Key);
Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
});
}
[Fact]
public void Read_WithSomeMissingValues_Works()
{
// Arrange
var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
var title = "Not found";
var status = 404;
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"}}";
var converter = new ProblemDetailsJsonConverter();
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
// Act
var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
});
}
[Fact]
public void Write_Works()
{
// Arrange
var traceId = "|37dd3dd5-4a9619f953c40a16.";
var value = new ProblemDetails
{
Title = "Not found",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Status = 404,
Detail = "Product not found",
Instance = "http://example.com/products/14",
Extensions =
{
{ "traceId", traceId },
{ "some-data", new[] { "value1", "value2" } }
}
};
var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status},\"detail\":\"{value.Detail}\",\"instance\":\"{JsonEncodedText.Encode(value.Instance)}\",\"traceId\":\"{traceId}\",\"some-data\":[\"value1\",\"value2\"]}}";
var converter = new ProblemDetailsJsonConverter();
var stream = new MemoryStream();
// Act
using (var writer = new Utf8JsonWriter(stream))
{
converter.Write(writer, value, JsonSerializerOptions);
}
// Assert
var actual = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(expected, actual);
}
[Fact]
public void Write_WithSomeMissingContent_Works()
{
// Arrange
var value = new ProblemDetails
{
Title = "Not found",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Status = 404,
};
var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status}}}";
var converter = new ProblemDetailsJsonConverter();
var stream = new MemoryStream();
// Act
using (var writer = new Utf8JsonWriter(stream))
{
converter.Write(writer, value, JsonSerializerOptions);
}
// Assert
var actual = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(expected, actual);
}
}
}

View File

@ -340,31 +340,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{ {
} }
[Fact(Skip = "https://github.com/aspnet/AspNetCore/pull/11460")] [Fact]
public override Task ActionsReturnBadRequest_WhenModelStateIsInvalid() public override Task ActionsReturnBadRequest_WhenModelStateIsInvalid()
{ {
return base.ActionsReturnBadRequest_WhenModelStateIsInvalid(); return base.ActionsReturnBadRequest_WhenModelStateIsInvalid();
} }
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38769")] [Fact]
public override Task ClientErrorResultFilterExecutesForStatusCodeResults() public override Task ClientErrorResultFilterExecutesForStatusCodeResults()
{ {
return base.ClientErrorResultFilterExecutesForStatusCodeResults(); return base.ClientErrorResultFilterExecutesForStatusCodeResults();
} }
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38769")] [Fact]
public override Task SerializingProblemDetails_IgnoresNullValuedProperties() public override Task SerializingProblemDetails_IgnoresNullValuedProperties()
{ {
return base.SerializingProblemDetails_IgnoresNullValuedProperties(); return base.SerializingProblemDetails_IgnoresNullValuedProperties();
} }
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38769")] [Fact]
public override Task SerializingProblemDetails_WithAllValuesSpecified() public override Task SerializingProblemDetails_WithAllValuesSpecified()
{ {
return base.SerializingProblemDetails_WithAllValuesSpecified(); return base.SerializingProblemDetails_WithAllValuesSpecified();
} }
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38769")] [Fact]
public override Task SerializingValidationProblemDetails_WithExtensionData() public override Task SerializingValidationProblemDetails_WithExtensionData()
{ {
return base.SerializingValidationProblemDetails_WithExtensionData(); return base.SerializingValidationProblemDetails_WithExtensionData();

View File

@ -4,7 +4,6 @@
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using FormatterWebSite.Controllers; using FormatterWebSite.Controllers;
using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Fact] [Fact]
public override Task Formatting_DictionaryType() => base.Formatting_DictionaryType(); public override Task Formatting_DictionaryType() => base.Formatting_DictionaryType();
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/11522")] [Fact]
public override Task Formatting_ProblemDetails() => base.Formatting_ProblemDetails(); public override Task Formatting_ProblemDetails() => base.Formatting_ProblemDetails();
[Fact] [Fact]