diff --git a/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs b/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs index 1c0ac938e5..d2eb14ebd9 100644 --- a/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs +++ b/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs @@ -1877,7 +1877,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } public partial class SystemTextJsonInputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter, Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy { - public SystemTextJsonInputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { } + public SystemTextJsonInputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options, Microsoft.Extensions.Logging.ILogger logger) { } Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy.ExceptionPolicy { get { throw null; } } public System.Text.Json.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs index 4b13a1b709..d584f47f82 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs @@ -8,6 +8,8 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters.Json; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Formatters { @@ -16,13 +18,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// public class SystemTextJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy { + private readonly ILogger _logger; + /// /// Initializes a new instance of . /// /// The . - public SystemTextJsonInputFormatter(JsonOptions options) + /// The . + public SystemTextJsonInputFormatter( + JsonOptions options, + ILogger logger) { SerializerOptions = options.JsonSerializerOptions; + _logger = logger; SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); @@ -67,6 +75,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions); } + catch (JsonException jsonException) + { + var path = jsonException.Path; + + var formatterException = new InputFormatterException(jsonException.Message, jsonException); + + context.ModelState.TryAddModelError(path, formatterException, context.Metadata); + + Log.JsonInputException(_logger, jsonException); + + return InputFormatterResult.Failure(); + } finally { if (inputStream is TranscodingReadStream transcoding) @@ -85,6 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } else { + Log.JsonInputSuccess(_logger, context.ModelType); return InputFormatterResult.Success(model); } } @@ -98,5 +119,29 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return new TranscodingReadStream(httpContext.Request.Body, encoding); } + + private static class Log + { + private static readonly Action _jsonInputFormatterException; + private static readonly Action _jsonInputSuccess; + + static Log() + { + _jsonInputFormatterException = LoggerMessage.Define( + LogLevel.Debug, + new EventId(1, "SystemTextJsonInputException"), + "JSON input formatter threw an exception: {Message}"); + _jsonInputSuccess = LoggerMessage.Define( + LogLevel.Debug, + new EventId(2, "SystemTextJsonInputSuccess"), + "JSON input formatter succeeded, deserializing to type '{TypeName}'"); + } + + public static void JsonInputException(ILogger logger, Exception exception) + => _jsonInputFormatterException(logger, exception.Message, exception); + + public static void JsonInputSuccess(ILogger logger, Type modelType) + => _jsonInputSuccess(logger, modelType.FullName, null); + } } } diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs b/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs index b94ff39557..79005a8389 100644 --- a/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs +++ b/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc options.Filters.Add(new UnsupportedContentTypeFilter()); // Set up default input formatters. - options.InputFormatters.Add(new SystemTextJsonInputFormatter(_jsonOptions.Value)); + options.InputFormatters.Add(new SystemTextJsonInputFormatter(_jsonOptions.Value, _loggerFactory.CreateLogger())); // Set up default output formatters. options.OutputFormatters.Add(new HttpNoContentOutputFormatter()); diff --git a/src/Mvc/Mvc.Core/test/Formatters/JsonInputFormatterTestBase.cs b/src/Mvc/Mvc.Core/test/Formatters/JsonInputFormatterTestBase.cs index e7190037e9..fd76501888 100644 --- a/src/Mvc/Mvc.Core/test/Formatters/JsonInputFormatterTestBase.cs +++ b/src/Mvc/Mvc.Core/test/Formatters/JsonInputFormatterTestBase.cs @@ -3,16 +3,21 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; +using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging.Testing; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.Formatters { - public abstract class JsonInputFormatterTestBase + public abstract class JsonInputFormatterTestBase : LoggedTest { [Theory] [InlineData("application/json", true)] @@ -102,6 +107,61 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Equal("abcd", stringValue); } + [Fact] + public virtual async Task JsonFormatter_EscapedKeys_Bracket() + { + var expectedKey = JsonFormatter_EscapedKeys_Bracket_Expected; + + // Arrange + var content = "[{\"It[s a key\":1234556}]"; + var formatter = GetInputFormatter(); + + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(IEnumerable>), httpContext); + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.True(result.HasError); + Assert.Collection( + formatterContext.ModelState.OrderBy(k => k.Key), + kvp => + { + Assert.Equal(expectedKey, kvp.Key); + }); + } + + [Fact] + public virtual async Task JsonFormatter_EscapedKeys() + { + var expectedKey = JsonFormatter_EscapedKeys_Expected; + + // Arrange + var content = "[{\"It\\\"s a key\": 1234556}]"; + var formatter = GetInputFormatter(); + + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext( + typeof(IEnumerable>), httpContext); + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.True(result.HasError); + Assert.Collection( + formatterContext.ModelState.OrderBy(k => k.Key), + kvp => + { + Assert.Equal(expectedKey, kvp.Key); + }); + } + [Fact] public virtual async Task JsonFormatterReadsDateTimeValue() { @@ -199,6 +259,33 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Equal(new int[] { 0, 23, 300 }, (IEnumerable)result.Model); } + [Fact] + public virtual async Task ReadAsync_ArrayOfObjects_HasCorrectKey() + { + // Arrange + var formatter = GetInputFormatter(); + + var content = "[{\"Age\": 5}, {\"Age\": 3}, {\"Age\": \"Cheese\"} ]"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(List), httpContext); + + var expectedKey = ReadAsync_ArrayOfObjects_HasCorrectKey_Expected; + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.True(result.HasError, "Model should have had an error!"); + Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key), + kvp => + { + Assert.Equal(expectedKey, kvp.Key); + Assert.Single(kvp.Value.Errors); + }); + } + [Fact] public virtual async Task ReadAsync_AddsModelValidationErrorsToModelState() { @@ -210,15 +297,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var httpContext = GetHttpContext(contentBytes); var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext); + var expectedKey = ReadAsync_AddsModelValidationErrorsToModelState_Expected; // Act var result = await formatter.ReadAsync(formatterContext); // Assert - Assert.True(result.HasError); - Assert.Equal( - "Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 44.", - formatterContext.ModelState["Age"].Errors[0].ErrorMessage); + Assert.True(result.HasError, "Model should have had an error!"); + Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key), + kvp => + { + Assert.Equal(expectedKey, kvp.Key); + Assert.Single(kvp.Value.Errors); + }); } [Fact] @@ -227,19 +318,23 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // Arrange var formatter = GetInputFormatter(); - var content = "[0, 23, 300]"; + var content = "[0, 23, 33767]"; var contentBytes = Encoding.UTF8.GetBytes(content); var httpContext = GetHttpContext(contentBytes); - var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext); + var formatterContext = CreateInputFormatterContext(typeof(short[]), httpContext); + + var expectedValue = ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected; // Act var result = await formatter.ReadAsync(formatterContext); // Assert - Assert.True(result.HasError); - Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage); - Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception); + Assert.True(result.HasError, "Model should have produced an error!"); + Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key), + kvp => { + Assert.Equal(expectedValue, kvp.Key); + }); } [Fact] @@ -253,15 +348,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var httpContext = GetHttpContext(contentBytes); var formatterContext = CreateInputFormatterContext(typeof(ComplexModel[]), httpContext, modelName: "names"); + var expectedKey = ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected; // Act var result = await formatter.ReadAsync(formatterContext); // Assert Assert.True(result.HasError); - Assert.Equal( - "Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 69.", - formatterContext.ModelState["names[1].Small"].Errors[0].ErrorMessage); + Assert.Collection( + formatterContext.ModelState.OrderBy(k => k.Key), + kvp => { + Assert.Equal(expectedKey, kvp.Key); + Assert.Single(kvp.Value.Errors); + }); } [Fact] @@ -318,6 +417,65 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Null(result.Model); } + [Fact] + public async Task ReadAsync_ComplexPoco() + { + // Arrange + var formatter = GetInputFormatter(); + + var content = "{ \"Id\": 5, \"Person\": { \"Name\": \"name\", \"Numbers\": [3, 2, \"Hamburger\"]} }"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext); + + var expectedKey = ReadAsync_ComplexPoco_Expected; + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.True(result.HasError, "Model should have had an error!"); + Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key), + kvp => { + Assert.Equal(expectedKey, kvp.Key); + Assert.Single(kvp.Value.Errors); + }); + } + + [Fact] + public virtual async Task ReadAsync_RequiredAttribute() + { + // Arrange + var formatter = GetInputFormatter(); + var content = "{ \"Id\": 5, \"Person\": {\"Numbers\": [3]} }"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext); + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.True(result.HasError, "Model should have had an error!"); + Assert.Single(formatterContext.ModelState["Person.Name"].Errors); + } + + internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; } + + internal abstract string JsonFormatter_EscapedKeys_Expected { get; } + + internal abstract string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected { get; } + + internal abstract string ReadAsync_AddsModelValidationErrorsToModelState_Expected { get; } + + internal abstract string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected { get; } + + internal abstract string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected { get; } + + internal abstract string ReadAsync_ComplexPoco_Expected { get; } + protected abstract TextInputFormatter GetInputFormatter(); protected static HttpContext GetHttpContext( @@ -356,6 +514,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue); } + protected sealed class ComplexPoco + { + public int Id { get; set; } + public Person Person{ get; set; } + } + + protected sealed class Person + { + [Required] + [JsonProperty(Required = Required.Always)] + public string Name { get; set; } + public IEnumerable Numbers { get; set; } + } + protected sealed class ComplexModel { public string Name { get; set; } diff --git a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs index 8cea7ff821..411725c7a9 100644 --- a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs +++ b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs @@ -1,40 +1,103 @@ // 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.Linq; +using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.AspNetCore.Mvc.Formatters { public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase { - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")] + [Fact] public override Task ReadAsync_AddsModelValidationErrorsToModelState() { return base.ReadAsync_AddsModelValidationErrorsToModelState(); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")] + [Fact] public override Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState() { return base.ReadAsync_InvalidArray_AddsOverflowErrorsToModelState(); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")] + [Fact] public override Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState() { return base.ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState(); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")] + [Fact] public override Task ReadAsync_UsesTryAddModelValidationErrorsToModelState() { return base.ReadAsync_UsesTryAddModelValidationErrorsToModelState(); } + [Fact(Skip = "https://github.com/dotnet/corefx/issues/38492")] + public override Task ReadAsync_RequiredAttribute() + { + // System.Text.Json does not yet support an equivalent of Required. + throw new NotImplementedException(); + } + + [Fact] + public override Task JsonFormatter_EscapedKeys() + { + return base.JsonFormatter_EscapedKeys(); + } + + [Fact] + public override Task JsonFormatter_EscapedKeys_Bracket() + { + return base.JsonFormatter_EscapedKeys_Bracket(); + } + + [Fact] + public async Task ReadAsync_SingleError() + { + // Arrange + var formatter = GetInputFormatter(); + + var content = "[5, 'seven', 3, notnum ]"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(List), httpContext); + + // Act + await formatter.ReadAsync(formatterContext); + + Assert.Collection( + formatterContext.ModelState.OrderBy(k => k), + kvp => + { + Assert.Equal("$[1]", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.StartsWith("''' is an invalid start of a value", error.ErrorMessage); + }); + } + protected override TextInputFormatter GetInputFormatter() { - return new SystemTextJsonInputFormatter(new JsonOptions()); + return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger()); } + + internal override string ReadAsync_AddsModelValidationErrorsToModelState_Expected => "$.Age"; + + internal override string JsonFormatter_EscapedKeys_Expected => "$[0]['It\\u0022s a key']"; + + internal override string JsonFormatter_EscapedKeys_Bracket_Expected => "$[0]['It[s a key']"; + + internal override string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected => "$[2].Age"; + + internal override string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected => "$[2]"; + + internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "$[1].Small"; + + internal override string ReadAsync_ComplexPoco_Expected => "$.Person.Numbers[2]"; } } diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs index 31a96631fc..8975f9f426 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs @@ -226,7 +226,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } else { - addMember = !path.EndsWith("." + member, StringComparison.Ordinal); + addMember = !path.EndsWith("." + member, StringComparison.Ordinal) + && !path.EndsWith("['" + member + "']", StringComparison.Ordinal) + && !path.EndsWith("[" + member + "]", StringComparison.Ordinal); } } } diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonLoggerExtensions.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonLoggerExtensions.cs index 14dcd92174..dfad01025e 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonLoggerExtensions.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonLoggerExtensions.cs @@ -8,13 +8,13 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson { internal static class NewtonsoftJsonLoggerExtensions { - private static readonly Action _jsonInputFormatterCrashed; + private static readonly Action _jsonInputFormatterException; private static readonly Action _jsonResultExecuting; static NewtonsoftJsonLoggerExtensions() { - _jsonInputFormatterCrashed = LoggerMessage.Define( + _jsonInputFormatterException = LoggerMessage.Define( LogLevel.Debug, new EventId(1, "JsonInputException"), "JSON input formatter threw an exception."); @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson public static void JsonInputException(this ILogger logger, Exception exception) { - _jsonInputFormatterCrashed(logger, exception); + _jsonInputFormatterException(logger, exception); } public static void JsonResultExecuting(this ILogger logger, object value) diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs index f9d79a6f6d..d9b1406a05 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs @@ -3,7 +3,7 @@ using System; using System.Buffers; -using System.IO; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -196,6 +196,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Equal(settings.DateTimeZoneHandling, actual.DateTimeZoneHandling); } + [Fact] + public override Task JsonFormatter_EscapedKeys() + { + return base.JsonFormatter_EscapedKeys(); + } + + [Fact] + public override Task JsonFormatter_EscapedKeys_Bracket() + { + return base.JsonFormatter_EscapedKeys_Bracket(); + } + [Theory] [InlineData(" ", true, true)] [InlineData(" ", false, false)] @@ -235,6 +247,39 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Equal(expectedMessage, modelError.ErrorMessage); } + [Fact] + public async Task ReadAsync_AllowMultipleErrors() + { + // Arrange + var content = "[5, 'seven', 3, 'notnum']"; + + var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true); + + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = GetHttpContext(contentBytes); + + var formatterContext = CreateInputFormatterContext(typeof(List), httpContext); + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.Collection( + formatterContext.ModelState.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("[1]", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.StartsWith("Could not convert string to integer:", error.ErrorMessage); + }, + kvp => + { + Assert.Equal("[3]", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.StartsWith("Could not convert string to integer:", error.ErrorMessage); + }); + } + [Fact] public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions() { @@ -325,6 +370,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters }); } + internal override string JsonFormatter_EscapedKeys_Expected => "[0]['It\"s a key']"; + + internal override string JsonFormatter_EscapedKeys_Bracket_Expected => "[0][\'It[s a key\']"; + + internal override string ReadAsync_AddsModelValidationErrorsToModelState_Expected => "Age"; + + internal override string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected => "[2].Age"; + + internal override string ReadAsync_ComplexPoco_Expected => "Person.Numbers[2]"; + + internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "names[1].Small"; + + internal override string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected => "[2]"; + private class Location { public int Id { get; set; } diff --git a/src/Mvc/Mvc.sln b/src/Mvc/Mvc.sln index 2a8c20227b..dd3d0a4fd3 100644 --- a/src/Mvc/Mvc.sln +++ b/src/Mvc/Mvc.sln @@ -325,6 +325,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server\src\Microsoft.Extensions.ApiDescription.Server.csproj", "{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Metadata", "..\Http\Metadata\src\Microsoft.AspNetCore.Metadata.csproj", "{464195B3-022A-4D19-9104-8C66CC882D67}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1841,6 +1843,18 @@ Global {D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|Mixed Platforms.Build.0 = Release|Any CPU {D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|x86.ActiveCfg = Release|Any CPU {D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|x86.Build.0 = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|x86.ActiveCfg = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Debug|x86.Build.0 = Debug|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|Any CPU.Build.0 = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|x86.ActiveCfg = Release|Any CPU + {464195B3-022A-4D19-9104-8C66CC882D67}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1972,6 +1986,7 @@ Global {C6F3BCE6-1EFD-4360-932B-B98573E78926} = {45CE788D-4B69-4F83-981C-F43D8F15B0F1} {637119E8-5BBB-4FC7-A372-DAF38FF5EBD9} = {5FE3048A-E96B-44F8-A7C4-FC590D7E04B4} {D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2} = {C15AA245-9E54-4FD6-90FF-B46F47779C46} + {464195B3-022A-4D19-9104-8C66CC882D67} = {5FE3048A-E96B-44F8-A7C4-FC590D7E04B4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}