Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make JSON case-insensitive #10727

Merged
merged 5 commits into from
Jun 3, 2019
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,11 @@ public partial interface IRequestFormLimitsPolicy : Microsoft.AspNetCore.Mvc.Fil
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; } }
rynowak marked this conversation as resolved.
Show resolved Hide resolved
}
public partial class JsonResult : Microsoft.AspNetCore.Mvc.ActionResult, Microsoft.AspNetCore.Mvc.IActionResult, Microsoft.AspNetCore.Mvc.Infrastructure.IStatusCodeActionResult
{
public JsonResult(object value) { }
Expand Down Expand Up @@ -881,7 +886,6 @@ public MvcOptions() { }
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 { } }
Expand Down Expand Up @@ -1872,15 +1876,15 @@ public StringOutputFormatter() { }
}
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]
public sealed override System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult> ReadRequestBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext context, System.Text.Encoding encoding) { throw null; }
}
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; }
Expand Down Expand Up @@ -3026,6 +3030,7 @@ public static partial class MvcCoreMvcBuilderExtensions
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; }
Expand All @@ -3039,6 +3044,7 @@ public static partial class MvcCoreMvcCoreBuilderExtensions
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ public static IMvcBuilder AddMvcOptions(
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ public static IMvcCoreBuilder AddMvcOptions(
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public class SystemTextJsonInputFormatter : TextInputFormatter, IInputFormatterE
/// <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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ public class SystemTextJsonOutputFormatter : TextOutputFormatter
/// <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);
Expand Down
20 changes: 16 additions & 4 deletions src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,33 @@ internal class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>, IPostConf
{
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)
Expand All @@ -66,13 +78,13 @@ public void Configure(MvcOptions options)
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ internal sealed class SystemTextJsonResultExecutor : IActionResultExecutor<JsonR
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;
}

Expand Down Expand Up @@ -100,7 +100,7 @@ private JsonSerializerOptions GetSerializerOptions(JsonResult result)
var serializerSettings = result.SerializerSettings;
if (serializerSettings == null)
{
return _mvcOptions.SerializerOptions;
return _options.JsonSerializerOptions;
}
else
{
Expand Down
31 changes: 31 additions & 0 deletions src/Mvc/Mvc.Core/src/JsonOptions.cs
Original file line number Diff line number Diff line change
@@ -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,
rynowak marked this conversation as resolved.
Show resolved Hide resolved

// Use camel casing for properties
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
}
}
29 changes: 6 additions & 23 deletions src/Mvc/Mvc.Core/src/MvcOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
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;
Expand Down Expand Up @@ -105,18 +103,18 @@ public MvcOptions()

/// <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>
Expand Down Expand Up @@ -361,21 +359,6 @@ public int MaxModelBindingRecursionDepth
}
}

/// <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();
Expand Down
2 changes: 1 addition & 1 deletion src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private static IServiceProvider CreateServices()
{
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(
Expand Down
Loading