每个请求的 ASP.NET Core API JSON 序列化程序设置

Posted

技术标签:

【中文标题】每个请求的 ASP.NET Core API JSON 序列化程序设置【英文标题】:ASP.NET Core API JSON serializersettings per request 【发布时间】:2017-12-03 08:12:28 【问题描述】:

基于请求中的某些值(标头或 url),我想更改我的 DTO 对象的序列化。 为什么?好吧,我已将[JsonProperty("A")] 应用于我的 DTO,但取决于它是否想要使用该属性的客户端(网站或移动应用程序)。 我开始了

services
.AddMvc()
.AddJsonOptions(opt =>

#if DEBUG
    opt.SerializerSettings.ContractResolver = new NoJsonPropertyNameContractResolver();
#endif

所以在调试时,我得到了带有完整属性名的 JSON。我使用JsonProperty 属性来缩短响应 JSON,它适用于反序列化回相同 DTO 的移动应用程序 (Xamarin)。 但是现在我有一个网站,它使用相同的 API 通过 jQuery 获取数据,但在那里我想处理 DTO 的完整属性名称,而不是 JsonProperty 属性中给出的名称。 网站和WebApi在同一个服务器,响应大一点也没问题。

我从一个中间件类开始对客户标头值做出反应,这很有效,但现在我不知道如何访问 JSON SerializerSettings。在网上搜索但找不到。

在搜索时,我阅读了有关 InputFormatters 和 OutputFormatters 以及内容协商的信息,但我不知道我必须往哪个方向发展。

我不想使用不同的设置两次部署相同的 API。 如果有帮助,我可以更改诸如 routesconfig 之类的内容。

更新 不仅 JSON 响应必须以两种不同的方式进行序列化,反序列化也必须以两种不同的方式进行。

【问题讨论】:

您是否有理由要在网站中使用不同的属性名称? 是的。对于应用程序,我们使用属性名称的简短版本来使 JSON 尽可能短。在应用程序中,JSON 被序列化回 DTO,因此在代码中我们有完整的属性名称。在网站中,我们使用 jQuery 来获取/发布/放置数据,因此我们需要完整的属性名称以保持 javascript 代码的可读性。我认为 Input 和 OutputFormatters 是要走的路。现在搜索如何初始化 JsonInputFormatter。 ***.com/questions/20622492/… 和自定义模型绑定器怎么样? 【参考方案1】:

这里有两个选项:

1。手动格式化

services.AddMvc().AddJsonOptions() 设置的选项在 DI 中注册,您可以将其注入到您的控制器和服务中:

public HomeController(IOptions<MvcJsonOptions> optionsAccessor)

    JsonSerializerSettings jsonSettings = optionsAccessor.Value.SerializerSettings;

要按请求覆盖这些序列化设置,您可以使用Json 方法或创建JsonResult 实例:

public IActionResult Get()

    return Json(data, new JsonSerializerSettings());

    return new JsonResult(data, new JsonSerializerSettings());

2。替换 JSON 输出的结果过滤器

public class ModifyResultFilter : IAsyncResultFilter

    public ModifyResultFilter(IOptions<MvcJsonOptions> optionsAccessor)
    
        _globalSettings = optionsAccessor.Value.SerializerSettings;
    

    public async Task OnResultExecutionAsync(
        ResultExecutingContext context,
        ResultExecutionDelegate next)
    
        var originResult = context.Result as JsonResult;

        context.Result = new JsonResult(originResult.Value, customSettings);

        await next();
    

在动作/控制器上使用它:

[ServiceFilter(typeof(ModifyResultFilter ))]
public IActionResult Index() 

或者按照documentation中的描述创建一个自定义属性:

[ModifyResultAttribute]
public IActionResult Index() 

不要忘记在 DI 中注册过滤器。

【讨论】:

谢谢,但这意味着要更改所有控制器(虽然还没有那么多) @ArieKanarie,我想了两次并添加了结果过滤器,所以唯一的改变可能是用属性标记控制器。 嗯,这也可能是一个解决方案。如果我没记错的话,我们可以通过context 属性访问标题,并且根据标题值我们可以使用不同的设置。也许这比我想出的解决方案要少。会想到我接受哪一个作为答案 现在我也想了两次,这只会处理结果(=输出),这意味着我们还必须为输入创建一个过滤器,因为客户端发送的 JSON 也因客户端而异。我认为我的问题并不清楚。 如果您只需要更改输出,那么我会推荐这个而不是我的答案。工作量会减少。【参考方案2】:

感谢 cmets 和答案。我找到了输入和输出格式化程序的解决方案。感谢http://rovani.net/Explicit-Model-Constructor/ 为我指明了正确的方向。

我创建了自己的输入和输出格式化程序,它们继承自 JsonInputFormatter,以保持尽可能多的功能相同。 在构造函数中,我设置了支持的媒体类型(使用了一些看起来像现有 JSON 的媒体类型)。 还必须覆盖 CreateJsonSerializer 以将 ContractResolver 设置为所需的(可以实现单例)。 必须这样做,因为在构造函数中更改 serializerSettings 会更改所有输入/输出格式化程序的序列化程序设置,这意味着默认的 JSON 格式化程序也将使用新的合同解析程序。 这样做也意味着您可以通过AddMvc().AddJsonOption() 设置一些默认的 JSON 选项

示例inputformatter,outputformatter使用相同的原理:

static MediaTypeHeaderValue protoMediaType = MediaTypeHeaderValue.Parse("application/jsonfull");

public JsonFullInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider) 
    : base(logger, serializerSettings, charPool, objectPoolProvider)

    this.SupportedMediaTypes.Clear();
    this.SupportedMediaTypes.Add(protoMediaType);


protected override JsonSerializer CreateJsonSerializer()

    var serializer = base.CreateJsonSerializer();            
    serializer.ContractResolver = new NoJsonPropertyNameContractResolver();

    return serializer;

根据上面提到的安装类 URL:

public class YourMvcOptionsSetup : IConfigureOptions<MvcOptions>

    private readonly ILoggerFactory _loggerFactory;
    private readonly JsonSerializerSettings _jsonSerializerSettings;
    private readonly ArrayPool<char> _charPool;
    private readonly ObjectPoolProvider _objectPoolProvider;

    public YourMvcOptionsSetup(ILoggerFactory loggerFactory, IOptions<MvcJsonOptions> jsonOptions, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider)
    
        //Validate parameters and set fields
    

    public void Configure(MvcOptions options)
    
        var jsonFullInputFormatter = new JsonFullInputFormatter(
            _loggerFactory.CreateLogger<JsonFullInputFormatter>(),
            _jsonSerializerSettings,
            _charPool,
            _objectPoolProvider
        );

        options.InputFormatters.Add(jsonFullInputFormatter);

        options.OutputFormatters.Add(new JsonFullOutputFormatter(
            _jsonSerializerSettings,
            _charPool
        ));
    

然后是一个扩展方法来注册它:

public static class MvcBuilderExtensions

    public static IMvcBuilder AddJsonFullFormatters(this IMvcBuilder builder)
    
        if (builder == null)
        
            throw new ArgumentNullException(nameof(builder));
        
        ServiceDescriptor descriptor = ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, YourMvcOptionsSetup>();
        builder.Services.TryAddEnumerable(descriptor);
        return builder;
    

拨打ConfigureServices

services.AddMvc(config =>

    config.RespectBrowserAcceptHeader = true; // To use the JsonFullFormatters if clients asks about it via Accept Header
)
.AddJsonFullFormatters() //Add our own JSON Formatters
.AddJsonOptions(opt =>

     //Set up some default options all JSON formatters must use (if any)
);

现在我们的 Xamarin 应用可以访问 webapi 并接收带有通过 JsonProperty 属性设置的(短)属性名称的 JSON。 在网站中,我们可以通过添加 Accept(get 调用)和 ContentType(post/put 调用)标头来获取完整的 JSON 属性名称。我们通过 jQuery 的$.ajaxSetup( 执行一次。

$.ajaxSetup(
    contentType: "application/jsonfull; charset=utf-8",
    headers:  'Accept': 'application/jsonfull' 
);

【讨论】:

以上是关于每个请求的 ASP.NET Core API JSON 序列化程序设置的主要内容,如果未能解决你的问题,请参考以下文章

根据 ASP.NET Core 请求标头中提供的 API 密钥授权用户

如何在 Web API Asp.NET Core 5 中持久化常用数据?

ASP.Net Core 2.1 API JWT 无 cookie 会话?

如何继续等待 API 请求响应值完成 ASP .Net Core MVC

ASP.NET Core Web API 在 API GET 请求中检索空白记录

asp.net core api PUT 请求从不命中方法