Pārlūkot izejas kodu

Make JSON case-insensitive (#10727)

* Make JSON case-insensitive

Fixes: #10724

The rationale for this change is that existing .NET client code for the
most part uses JSON.NET with its default settings (preserve property
casing). This includes the WebAPI client - which we're encouraging
everyone to use. It's not really reasonable for us to break everyone
using webapi client.

* Make separate options and add extension method

* fixit

* fix build

* fix text
Ryan Nowak 6 gadi atpakaļ
vecāks
revīzija
670865d2a8
23 mainītis faili ar 174 papildinājumiem un 60 dzēšanām
  1. 9 3
      src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs
  2. 24 0
      src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreMvcBuilderExtensions.cs
  3. 24 0
      src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs
  4. 3 3
      src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs
  5. 3 3
      src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
  6. 16 4
      src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs
  7. 4 4
      src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
  8. 31 0
      src/Mvc/Mvc.Core/src/JsonOptions.cs
  9. 6 23
      src/Mvc/Mvc.Core/src/MvcOptions.cs
  10. 1 1
      src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs
  11. 1 1
      src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs
  12. 1 1
      src/Mvc/Mvc.Core/test/CreatedResultTests.cs
  13. 1 1
      src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs
  14. 1 1
      src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs
  15. 1 1
      src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs
  16. 1 1
      src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs
  17. 1 1
      src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs
  18. 1 1
      src/Mvc/Mvc.Core/test/Infrastructure/SystemTextJsonResultExecutorTest.cs
  19. 4 4
      src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs
  20. 3 5
      src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs
  21. 23 0
      src/Mvc/test/Mvc.FunctionalTests/JsonInputFormatterTestBase.cs
  22. 0 2
      src/Mvc/test/WebSites/BasicWebSite/Startup.cs
  23. 15 0
      src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs

+ 9 - 3
src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs

@@ -814,6 +814,11 @@ namespace Microsoft.AspNetCore.Mvc
     public partial interface IRequestSizePolicy : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata
     {
     }
+    public partial class JsonOptions
+    {
+        public JsonOptions() { }
+        public System.Text.Json.Serialization.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
+    }
     public partial class JsonResult : Microsoft.AspNetCore.Mvc.ActionResult, Microsoft.AspNetCore.Mvc.IActionResult, Microsoft.AspNetCore.Mvc.Infrastructure.IStatusCodeActionResult
     {
         public JsonResult(object value) { }
@@ -881,7 +886,6 @@ namespace Microsoft.AspNetCore.Mvc
         public bool RequireHttpsPermanent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
         public bool RespectBrowserAcceptHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
         public bool ReturnHttpNotAcceptable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
-        public System.Text.Json.Serialization.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
         public int? SslPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
         public bool SuppressAsyncSuffixInActionNames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
         public bool SuppressImplicitRequiredAttributeForNonNullableReferenceTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
@@ -1872,7 +1876,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.MvcOptions options) { }
+        public SystemTextJsonInputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { }
         Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy.ExceptionPolicy { get { throw null; } }
         public System.Text.Json.Serialization.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
         [System.Diagnostics.DebuggerStepThroughAttribute]
@@ -1880,7 +1884,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
     }
     public partial class SystemTextJsonOutputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter
     {
-        public SystemTextJsonOutputFormatter(Microsoft.AspNetCore.Mvc.MvcOptions options) { }
+        public SystemTextJsonOutputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { }
         public System.Text.Json.Serialization.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
         [System.Diagnostics.DebuggerStepThroughAttribute]
         public sealed override System.Threading.Tasks.Task WriteResponseBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext context, System.Text.Encoding selectedEncoding) { throw null; }
@@ -3026,6 +3030,7 @@ namespace Microsoft.Extensions.DependencyInjection
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder AddApplicationPart(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Reflection.Assembly assembly) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder AddControllersAsServices(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder AddFormatterMappings(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.Formatters.FormatterMappings> setupAction) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IMvcBuilder AddJsonOptions(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.JsonOptions> configure) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder AddMvcOptions(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.MvcOptions> setupAction) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder ConfigureApiBehaviorOptions(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.ApiBehaviorOptions> setupAction) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcBuilder ConfigureApplicationPartManager(this Microsoft.Extensions.DependencyInjection.IMvcBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager> setupAction) { throw null; }
@@ -3039,6 +3044,7 @@ namespace Microsoft.Extensions.DependencyInjection
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder AddControllersAsServices(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder AddFormatterMappings(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder AddFormatterMappings(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.Formatters.FormatterMappings> setupAction) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder AddJsonOptions(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.JsonOptions> configure) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder AddMvcOptions(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.MvcOptions> setupAction) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder ConfigureApiBehaviorOptions(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.ApiBehaviorOptions> setupAction) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder ConfigureApplicationPartManager(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager> setupAction) { throw null; }

+ 24 - 0
src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreMvcBuilderExtensions.cs

@@ -42,6 +42,30 @@ namespace Microsoft.Extensions.DependencyInjection
             return builder;
         }
 
+        /// <summary>
+        /// Configures <see cref="JsonOptions"/> for the specified <paramref name="builder"/>.
+        /// </summary>
+        /// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
+        /// <param name="configure">An <see cref="Action"/> to configure the <see cref="JsonOptions"/>.</param>
+        /// <returns>The <see cref="IMvcBuilder"/>.</returns>
+        public static IMvcBuilder AddJsonOptions(
+            this IMvcBuilder builder,
+            Action<JsonOptions> configure)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+
+            if (configure == null)
+            {
+                throw new ArgumentNullException(nameof(configure));
+            }
+
+            builder.Services.Configure(configure);
+            return builder;
+        }
+
         /// <summary>
         /// Configures <see cref="FormatterMappings"/> for the specified <paramref name="builder"/>.
         /// </summary>

+ 24 - 0
src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs

@@ -44,6 +44,30 @@ namespace Microsoft.Extensions.DependencyInjection
             return builder;
         }
 
+        /// <summary>
+        /// Configures <see cref="JsonOptions"/> for the specified <paramref name="builder"/>.
+        /// </summary>
+        /// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
+        /// <param name="configure">An <see cref="Action"/> to configure the <see cref="JsonOptions"/>.</param>
+        /// <returns>The <see cref="IMvcBuilder"/>.</returns>
+        public static IMvcCoreBuilder AddJsonOptions(
+            this IMvcCoreBuilder builder,
+            Action<JsonOptions> configure)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+
+            if (configure == null)
+            {
+                throw new ArgumentNullException(nameof(configure));
+            }
+
+            builder.Services.Configure(configure);
+            return builder;
+        }
+
         /// <summary>
         /// Adds services to support <see cref="FormatterMappings"/>.
         /// </summary>

+ 3 - 3
src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs

@@ -19,10 +19,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
         /// <summary>
         /// Initializes a new instance of <see cref="SystemTextJsonInputFormatter"/>.
         /// </summary>
-        /// <param name="options">The <see cref="MvcOptions"/>.</param>
-        public SystemTextJsonInputFormatter(MvcOptions options)
+        /// <param name="options">The <see cref="JsonOptions"/>.</param>
+        public SystemTextJsonInputFormatter(JsonOptions options)
         {
-            SerializerOptions = options.SerializerOptions;
+            SerializerOptions = options.JsonSerializerOptions;
 
             SupportedEncodings.Add(UTF8EncodingWithoutBOM);
             SupportedEncodings.Add(UTF16EncodingLittleEndian);

+ 3 - 3
src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs

@@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
         /// <summary>
         /// Initializes a new <see cref="SystemTextJsonOutputFormatter"/> instance.
         /// </summary>
-        /// <param name="options">The <see cref="MvcOptions"/>.</param>
-        public SystemTextJsonOutputFormatter(MvcOptions options)
+        /// <param name="options">The <see cref="JsonOptions"/>.</param>
+        public SystemTextJsonOutputFormatter(JsonOptions options)
         {
-            SerializerOptions = options.SerializerOptions;
+            SerializerOptions = options.JsonSerializerOptions;
 
             SupportedEncodings.Add(Encoding.UTF8);
             SupportedEncodings.Add(Encoding.Unicode);

+ 16 - 4
src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs

@@ -25,21 +25,33 @@ namespace Microsoft.AspNetCore.Mvc
     {
         private readonly IHttpRequestStreamReaderFactory _readerFactory;
         private readonly ILoggerFactory _loggerFactory;
+        private readonly IOptions<JsonOptions> _jsonOptions;
 
         public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
-            : this(readerFactory, NullLoggerFactory.Instance)
+            : this(readerFactory, NullLoggerFactory.Instance, Options.Create(new JsonOptions()))
         {
         }
 
-        public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory)
+        public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory, IOptions<JsonOptions> jsonOptions)
         {
             if (readerFactory == null)
             {
                 throw new ArgumentNullException(nameof(readerFactory));
             }
 
+            if (loggerFactory == null)
+            {
+                throw new ArgumentNullException(nameof(loggerFactory));
+            }
+
+            if (jsonOptions == null)
+            {
+                throw new ArgumentNullException(nameof(jsonOptions));
+            }
+
             _readerFactory = readerFactory;
             _loggerFactory = loggerFactory;
+            _jsonOptions = jsonOptions;
         }
 
         public void Configure(MvcOptions options)
@@ -66,13 +78,13 @@ namespace Microsoft.AspNetCore.Mvc
             options.Filters.Add(new UnsupportedContentTypeFilter());
 
             // Set up default input formatters.
-            options.InputFormatters.Add(new SystemTextJsonInputFormatter(options));
+            options.InputFormatters.Add(new SystemTextJsonInputFormatter(_jsonOptions.Value));
 
             // Set up default output formatters.
             options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
             options.OutputFormatters.Add(new StringOutputFormatter());
             options.OutputFormatters.Add(new StreamOutputFormatter());
-            options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(options));
+            options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(_jsonOptions.Value));
 
             // Set up ValueProviders
             options.ValueProviderFactories.Add(new FormValueProviderFactory());

+ 4 - 4
src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs

@@ -23,14 +23,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
             Encoding = Encoding.UTF8
         }.ToString();
 
-        private readonly MvcOptions _mvcOptions;
+        private readonly JsonOptions _options;
         private readonly ILogger<SystemTextJsonResultExecutor> _logger;
 
         public SystemTextJsonResultExecutor(
-            IOptions<MvcOptions> mvcOptions,
+            IOptions<JsonOptions> options,
             ILogger<SystemTextJsonResultExecutor> logger)
         {
-            _mvcOptions = mvcOptions.Value;
+            _options = options.Value;
             _logger = logger;
         }
 
@@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
             var serializerSettings = result.SerializerSettings;
             if (serializerSettings == null)
             {
-                return _mvcOptions.SerializerOptions;
+                return _options.JsonSerializerOptions;
             }
             else
             {

+ 31 - 0
src/Mvc/Mvc.Core/src/JsonOptions.cs

@@ -0,0 +1,31 @@
+// 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.Text.Json.Serialization;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+    public class JsonOptions
+    {
+        /// <summary>
+        /// Gets the <see cref="System.Text.Json.Serialization.JsonSerializerOptions"/> used by <see cref="SystemTextJsonInputFormatter"/> and
+        /// <see cref="SystemTextJsonOutputFormatter"/>.
+        /// </summary>
+        public JsonSerializerOptions JsonSerializerOptions { get; } = new JsonSerializerOptions
+        {
+            // Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
+            // from deserialization errors that might occur from deeply nested objects.
+            // This value is the same for model binding and Json.Net's serialization.
+            MaxDepth = MvcOptions.DefaultMaxModelBindingRecursionDepth,
+
+            // We're using case-insensitive because there's a TON of code that there that does uses JSON.NET's default
+            // settings (preserve case) - including the WebAPIClient. This worked when we were using JSON.NET + camel casing
+            // because JSON.NET is case-insensitive by default.
+            PropertyNameCaseInsensitive = true,
+
+            // Use camel casing for properties
+            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+        };
+    }
+}

+ 6 - 23
src/Mvc/Mvc.Core/src/MvcOptions.cs

@@ -5,8 +5,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
-using System.Text.Json;
-using System.Text.Json.Serialization;
 using Microsoft.AspNetCore.Mvc.ApplicationModels;
 using Microsoft.AspNetCore.Mvc.Controllers;
 using Microsoft.AspNetCore.Mvc.Filters;
@@ -105,18 +103,18 @@ namespace Microsoft.AspNetCore.Mvc
 
         /// <summary>
         /// Gets or sets a value that detemines if the inference of <see cref="RequiredAttribute"/> for
-        /// for properties and parameters of non-nullable reference types is suppressed. If <c>false</c> 
-        /// (the default), then all non-nullable reference types will behave as-if <c>[Required]</c> has 
-        /// been applied. If <c>true</c>, this behavior will be suppressed; nullable reference types and 
+        /// for properties and parameters of non-nullable reference types is suppressed. If <c>false</c>
+        /// (the default), then all non-nullable reference types will behave as-if <c>[Required]</c> has
+        /// been applied. If <c>true</c>, this behavior will be suppressed; nullable reference types and
         /// non-nullable reference types will behave the same for the purposes of validation.
         /// </summary>
         /// <remarks>
         /// <para>
-        /// This option controls whether MVC model binding and validation treats nullable and non-nullable 
-        /// reference types differently. 
+        /// This option controls whether MVC model binding and validation treats nullable and non-nullable
+        /// reference types differently.
         /// </para>
         /// <para>
-        /// By default, MVC will treat a non-nullable reference type parameters and properties as-if 
+        /// By default, MVC will treat a non-nullable reference type parameters and properties as-if
         /// <c>[Required]</c> has been applied, resulting in validation errors when no value was bound.
         /// </para>
         /// <para>
@@ -361,21 +359,6 @@ namespace Microsoft.AspNetCore.Mvc
             }
         }
 
-        /// <summary>
-        /// Gets the <see cref="JsonSerializerOptions"/> used by <see cref="SystemTextJsonInputFormatter"/> and
-        /// <see cref="SystemTextJsonOutputFormatter"/>.
-        /// </summary>
-        public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions
-        {
-            // Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
-            // from deserialization errors that might occur from deeply nested objects.
-            // This value is the same for model binding and Json.Net's serialization.
-            MaxDepth = DefaultMaxModelBindingRecursionDepth,
-
-            // Use camel casing for properties
-            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-        };
-
         IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator() => _switches.GetEnumerator();
 
         IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();

+ 1 - 1
src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs

@@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc
         {
             var options = Options.Create(new MvcOptions());
             options.Value.OutputFormatters.Add(new StringOutputFormatter());
-            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
             var services = new ServiceCollection();
             services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(

+ 1 - 1
src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs

@@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc
         {
             var options = Options.Create(new MvcOptions());
             options.Value.OutputFormatters.Add(new StringOutputFormatter());
-            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
             var services = new ServiceCollection();
             services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(

+ 1 - 1
src/Mvc/Mvc.Core/test/CreatedResultTests.cs

@@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc
         {
             var options = Options.Create(new MvcOptions());
             options.Value.OutputFormatters.Add(new StringOutputFormatter());
-            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
             var services = new ServiceCollection();
             services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(

+ 1 - 1
src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs

@@ -465,7 +465,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
                 // Set up default output formatters.
                 MvcOptions.OutputFormatters.Add(new HttpNoContentOutputFormatter());
                 MvcOptions.OutputFormatters.Add(new StringOutputFormatter());
-                MvcOptions.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+                MvcOptions.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
                 // Set up default mapping for json extensions to content type
                 MvcOptions.FormatterMappings.SetMediaTypeMappingForFormat(

+ 1 - 1
src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs

@@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
 
         protected override TextInputFormatter GetInputFormatter()
         {
-            return new SystemTextJsonInputFormatter(new MvcOptions());
+            return new SystemTextJsonInputFormatter(new JsonOptions());
         }
     }
 }

+ 1 - 1
src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs

@@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
     {
         protected override TextOutputFormatter GetOutputFormatter()
         {
-            return new SystemTextJsonOutputFormatter(new MvcOptions());
+            return new SystemTextJsonOutputFormatter(new JsonOptions());
         }
 
         [Theory]

+ 1 - 1
src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs

@@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc
         {
             var options = Options.Create(new MvcOptions());
             options.Value.OutputFormatters.Add(new StringOutputFormatter());
-            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
             var services = new ServiceCollection();
             services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(

+ 1 - 1
src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs

@@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc
         {
             var options = Options.Create(new MvcOptions());
             options.Value.OutputFormatters.Add(new StringOutputFormatter());
-            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new MvcOptions()));
+            options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
 
             var services = new ServiceCollection();
             services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(

+ 1 - 1
src/Mvc/Mvc.Core/test/Infrastructure/SystemTextJsonResultExecutorTest.cs

@@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
     {
         protected override IActionResultExecutor<JsonResult> CreateExecutor(ILoggerFactory loggerFactory)
         {
-            return new SystemTextJsonResultExecutor(Options.Create(new MvcOptions()), loggerFactory.CreateLogger<SystemTextJsonResultExecutor>());
+            return new SystemTextJsonResultExecutor(Options.Create(new JsonOptions()), loggerFactory.CreateLogger<SystemTextJsonResultExecutor>());
         }
 
         protected override object GetIndentedSettings()

+ 4 - 4
src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs

@@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
 {
     internal class SystemTextJsonHelper : IJsonHelper
     {
-        private readonly MvcOptions _mvcOptions;
+        private readonly JsonOptions _options;
 
-        public SystemTextJsonHelper(IOptions<MvcOptions> mvcOptions)
+        public SystemTextJsonHelper(IOptions<JsonOptions> options)
         {
-            _mvcOptions = mvcOptions.Value;
+            _options = options.Value;
         }
 
         /// <inheritdoc />
@@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
         {
             // JsonSerializer always encodes non-ASCII chars, so we do not need
             // to do anything special with the SerializerOptions
-            var json = JsonSerializer.ToString(value, _mvcOptions.SerializerOptions);
+            var json = JsonSerializer.ToString(value, _options.JsonSerializerOptions);
             return new HtmlString(json);
         }
     }

+ 3 - 5
src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs

@@ -1,8 +1,6 @@
-
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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.Text.Json.Serialization;
 using Microsoft.Extensions.Options;
 
@@ -12,8 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
     {
         protected override IJsonHelper GetJsonHelper()
         {
-            var mvcOptions = new MvcOptions { SerializerOptions = { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } };
-            return new SystemTextJsonHelper(Options.Create(mvcOptions));
+            var options = new JsonOptions() { JsonSerializerOptions = { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } };
+            return new SystemTextJsonHelper(Options.Create(options));
         }
     }
 }

+ 23 - 0
src/Mvc/test/Mvc.FunctionalTests/JsonInputFormatterTestBase.cs

@@ -8,6 +8,7 @@ using System.Net.Http;
 using System.Text;
 using System.Text.Json.Serialization;
 using System.Threading.Tasks;
+using FormatterWebSite.Controllers;
 using Microsoft.AspNetCore.Hosting;
 using Xunit;
 
@@ -98,6 +99,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
             Assert.Equal(expected, responseBody);
         }
 
+        [Fact]
+        public async Task JsonInputFormatter_RoundtripsPocoModel()
+        {
+            // Arrange
+            var expected = new JsonFormatterController.SimpleModel()
+            {
+                Id = 18,
+                Name = "James",
+                StreetName = "JnK",
+            };
+
+            // Act
+            var response = await Client.PostAsJsonAsync("http://localhost/JsonFormatter/RoundtripSimpleModel/", expected);
+            var actual = await response.Content.ReadAsAsync<JsonFormatterController.SimpleModel>();
+
+            // Assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            Assert.Equal(expected.Id, actual.Id);
+            Assert.Equal(expected.Name, actual.Name);
+            Assert.Equal(expected.StreetName, actual.StreetName);
+        }
+
         [Fact]
         public async Task JsonInputFormatter_Returns415UnsupportedMediaType_ForEmptyContentType()
         {

+ 0 - 2
src/Mvc/test/WebSites/BasicWebSite/Startup.cs

@@ -13,8 +13,6 @@ namespace BasicWebSite
         // Set up application services
         public void ConfigureServices(IServiceCollection services)
         {
-            services.AddRouting();
-
             services.AddMvc()
                 .SetCompatibilityVersion(CompatibilityVersion.Latest)
                 .AddNewtonsoftJson()

+ 15 - 0
src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs

@@ -64,5 +64,20 @@ namespace FormatterWebSite.Controllers
 
             return Content(value.ToString());
         }
+
+        [HttpPost]
+        public ActionResult<SimpleModel> RoundtripSimpleModel([FromBody] SimpleModel model)
+        {
+            return model;
+        }
+
+        public class SimpleModel
+        {
+            public int Id { get; set; }
+
+            public string Name { get; set; }
+
+            public string StreetName { get; set; }
+        }
     }
 }