aspnet / Mvc

[Archived] ASP.NET Core MVC is a model view controller framework for building dynamic web sites with clean separation of concerns, including the merged MVC, Web API, and Web Pages w/ Razor. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
5.62k stars 2.14k forks source link

Ability to have different JsonSerializerSettings for input and output missing #4562

Closed kwaclaw closed 7 years ago

kwaclaw commented 8 years ago

Until recently I was able to have different settings, but now the serializer settings on the Json formatters are not accessible anymore and I have to use AddJsonOptions(). This would be fine if MvcJsonOptions had different SerializerSettings for input and output.

What are the alternatives? Issue #4492 does not really explain it in a way that an "outsider" can easily understand.

kichalla commented 8 years ago

Until recently I was able to have different settings

Curious what settings are you setting differently between input & output formatters?

One of the reasons for both the input & output formatters to share the same settings is performance. Json.net serializer builds json contracts for types it serializes/deserializes. Having separate settings means having this cache in 2 places.

You can new up the input and output formatters with your custom serializer settings and add them to the global formatters list.

You can use JsonSerializerSettingsProvider to create the default serializer settings that MVC creates. https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs

kwaclaw commented 8 years ago

In my case I have different TypeNameHandlig settings. On input I use TypeNameHandling.All with a custom SerializationBinder that can process type meta-data for certain scenarios. But I dont want to add any type meta-data to the output. So currently my application is broken because the Javascript client does not expect this.

I would expect that input and output are sufficiently different scenarios to warrant differences in settings.

kwaclaw commented 8 years ago

Newing up my own formatter instances poses the question: what to supply for the other constructor arguments. Any guidance? Any way to use the same argument instances already used by the defaults?

dougbu commented 8 years ago

Any guidance? Any way to use the same argument instances already used by the defaults?

In general, you should set JsonSerializerSettings as your application requires in its Startup class.

If you need to create non-default JsonInputFormatter or JsonOutputFormatter instances, use JsonSerializerSettingsProvider (in RC2) to initialize the JsonSerializerSettings instances and get the remaining constructor parameters from DI e.g. adding parameters to the controller constructor.

BTW what settings would you like to set differently for input and output formatters?

kwaclaw commented 8 years ago

TypeNameHandling and SerializationBinder need to be different in my case, as described above. I would think this is not an uncommon scenario.

JamesNK commented 8 years ago

One of the reasons for both the input & output formatters to share the same settings is performance.

If you're worried about that then by default the input and output formatter could have the same instance of JsonSerializerSettings, and if a dev wants to they can set a different JsonSerializerSettings on the inputter/outputter then they can go ahead and do that.

Also by default the contract resolver on JsonSerializerSettings is null so it won't override the default contract resolver and there won't be any perf hit for reflection over the same object multiple times. You will only get that situation if a user explicitly sets a new contract resolver on their JsonSerializerSettings.

Eilon commented 8 years ago

Putting in Backlog for now. We will reconsider this in a future release (we can easily add new properties that are backwards-compatible).

jods4 commented 8 years ago

I was going to open an issue exactly for that.

My use-case is the reverse of @kwaclaw. I want to set TypeNameHandling to Objects for output because inside our SPA, a JSON reviver instantiates the correct class, complete with methods, validation, proper date deserialization, etc. On the other hand I want TypeNameHandling to None for input because ASP.NET doesn't need this information and it is a security risk to let JSON.NET blindly process it.

javiercn commented 8 years ago

I think these scenarios can be achieved today.

public class CustomSerializerSettingsSetup : IConfigureOptions<MvcOptions>
{
    private readonly ILoggerFactory _loggerFactory;
    private readonly ArrayPool<char> _charPool;
    private readonly ObjectPoolProvider _objectPoolProvider;

    public CustomSerializerSettingsSetup(
        ILoggerFactory loggerFactory,
        ArrayPool<char> charPool,
        ObjectPoolProvider objectPoolProvider)
    {
        _loggerFactory = loggerFactory;
        _charPool = charPool;
        _objectPoolProvider = objectPoolProvider;
    }

    public void Configure(MvcOptions options)
    {
        options.OutputFormatters.RemoveType<JsonOutputFormatter>();
        options.InputFormatters.RemoveType<JsonInputFormatter>();
        options.InputFormatters.RemoveType<JsonPatchInputFormatter>();

        var outputSettings = new JsonSerializerSettings();
        options.OutputFormatters.Add(new JsonOutputFormatter(outputSettings, _charPool));

        var inputSettings = new JsonSerializerSettings();
        var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
        options.InputFormatters.Add(new JsonInputFormatter(
            jsonInputLogger,
            inputSettings,
            _charPool,
            _objectPoolProvider));

        var jsonInputPatchLogger = _loggerFactory.CreateLogger<JsonPatchInputFormatter>();
        options.InputFormatters.Add(new JsonPatchInputFormatter(
            jsonInputPatchLogger,
            inputSettings,
            _charPool,
            _objectPoolProvider));
    }
}

On your startup class you simply do this when configuring services:

services.TryAddEnumerable(
    ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, CustomSerializerSettingsSetup>());

After you called AddMvc()

dougbu commented 8 years ago

One correction: Do not use new JsonSerializerSettings(); always call JsonSerialierSettingsProvider.CreateSerializerSettings() instead.

javiercn commented 8 years ago

@doughu is correct, I was oversimplifying.

weitzhandler commented 7 years ago

@dougbu If I have a subclass of JsonSerializerSettings, how can I clone all the properties provided by JsonSerializerSettingsProvider.CreateSerializerSettings()?

dougbu commented 7 years ago

@weitzhandler either copy the properties of the JsonSerializerSettings instance JsonSerializerSettingsProvider.CreateSerializerSettings() returns into your subclass or file a request at https://github.com/JamesNK/Newtonsoft.Json/issues for a protected copy constructor in the JsonSerializerSettings class.

weitzhandler commented 7 years ago

Here you go, please participate, thanks.

rynowak commented 7 years ago

I'm not sure that there's anything left to do here, it's possible use different sets of settings for input and output as shown here https://github.com/aspnet/Mvc/issues/4562#issuecomment-226100352

Recommend Closing /cc @Eilon