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

Json MVC model binding should have an option to respect non-nullable reference types when they are used #47178

Closed
rathga opened this issue Jan 16, 2021 · 2 comments

Comments

@rathga
Copy link
Contributor

rathga commented Jan 16, 2021

Summary

When deserializing Json to DTOs in MVC model binding, for non-nullable value type members null Json values are rejected, a nice error message is added to ModelState and a 400 error returned. However, for non-nullable reference types null values are let through which can then trip ArgumentNullExceptions in object constructors or property setters leading to a more cryptic 500 error rather than producing a 400 and a friendlier ModelState error.

An option could be added to (probably) JsonSerializerOptions so that it respects non-nullable reference types and throws a JsonException when encountering a null value, rather than binding the null value.

Motivation and goals

  • Current work around is to always use nullable reference types for all reference type members in DTOs, and defer null checks until after the constructor in validation or business logic. The DTO is unable to defend its not-null invariants itself, leading to pollution of the codebase with null checks elsewhere. Effectively this makes using nullable reference types not usable/useful for controller action DTOs.

  • An option could be added to (probably) JsonSerializerOptions so that it respects non-nullable reference types and throws a JsonException when encountering a null value, rather than binding the null value.

e.g. in Startup.cs

services.AddControllersWithViews()
    .AddJsonOptions(cfg => 
        cfg.JsonSerializerOptions.RespectNonNullableReferenceTypes = true
        );
  • This may be more aptly posted in the runtime repo as presumably the changes need to be in System.Text.Json, but as it is impacting me in my controllers I thought it would make sense to get feedback here first.

Examples

This small console app demonstrates the issue:

namespace JsonNullable
{
    static class Program
    {
        public record NullableInt(int? someNumber);
        public record NonNullableInt(int someNumber);

        public record NullableString(string? SomeString);
        public record NonNullableString
        {
            public string SomeString { get; }
            public NonNullableString(string someString) =>
                SomeString = someString ?? throw new ArgumentNullException(nameof(someString));
        }

        static void Main(string[] args)
        {
            var nullableInt = new NullableInt(null);
            var jsonInt = JsonSerializer.Serialize(nullableInt);
            var nonNullableInt = JsonSerializer.Deserialize<NonNullableInt>(jsonInt); 
            // ^^ throws Cannot get the value of a token type 'Null' as a number.
            // Gets handled gracefully by MVC model binding

            var nullableString = new NullableString(null);
            var jsonString = JsonSerializer.Serialize(nullableString); 
            var nonNullableString = JsonSerializer.Deserialize<NonNullableString>(jsonString);
            // ^^ throws ArgumentNullException from constructor
            // Causes 500 exception in MVC, forcing the null check to be removed from the DTO and performed after binding
        }
    }
}
@mkArtakMSFT mkArtakMSFT transferred this issue from dotnet/aspnetcore Jan 19, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Jan 19, 2021
@mkArtakMSFT
Copy link
Member

@ericstj FYI

@layomia
Copy link
Contributor

layomia commented Feb 3, 2021

Duplicate of #1256.

@layomia layomia closed this as completed Feb 3, 2021
@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Feb 3, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Mar 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants