将 JSON post 模型解包到动作参数

Unpacking a JSON post model to action parameters

我的任务是将大型框架 ASP.NET MVC 应用程序升级到 .NET 5。大部分工作已完成,但我 运行 遇到 HTTP 问题 POST 从前端调用 JSON 个对象。

这些 POST 调用中的大多数都有这样的 JSON 正文:

{
   "param1" : "hello",
   "param2" : "world",
   "param3" : 123
}

其对应的控制器动作如下所示:

public ActionResult SaveData(string param1, string param2, int param3)
{
    //Do save stuff
}

这在 ASP.NET Core 中不起作用。参数保持为空。我确实找到了一个解决方案,就是将这些参数封装在一个模型对象中,并将其作为唯一具有 [FromBody] 属性的参数。但是,此应用程序有数百个具有各种参数的操作,我真的不希望必须为所有这些编写模型。

所以,我正在寻找一种方法来告诉 ASP.NET 将这些 JSON 对象的属性视为相应操作的参数。我用谷歌搜索了一下,但找不到任何有用的东西。有什么办法可以做到这一点,还是我必须为每个动作编写模型?

我的一位同事发挥了他的 Google-fu 并最终找到了 this library,这正是我所需要的。

您可以为此创建自定义 ValueProvider

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;

public class JsonValueProviderFactory : IValueProviderFactory
{
    public async Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        string contentType = context.ActionContext.HttpContext.Request.Headers[HeaderNames.ContentType].FirstOrDefault();

        bool isJson = contentType == null
            ? false
            : contentType.StartsWith(MediaTypeNames.Application.Json, StringComparison.OrdinalIgnoreCase);

        if (isJson)
        {
            context.ActionContext.HttpContext.Request.EnableBuffering();
            using (var reader = new StreamReader(context.ActionContext.HttpContext.Request.Body, Encoding.UTF8, false, 1024, true))
            {
                string body = await reader.ReadToEndAsync();
                var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
                context.ActionContext.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin); // rewind

                var valueProvider = new JsonValueProvider(values);
                context.ValueProviders.Add(valueProvider);
            }
        }
    }
}

//todo: implement better logic for nested objects
public class JsonValueProvider : IValueProvider
{
    private Dictionary<string, object> _values;

    public JsonValueProvider(Dictionary<string, object> values)
    {
        _values = new Dictionary<string, object>(values, StringComparer.OrdinalIgnoreCase);
    }

    public bool ContainsPrefix(string prefix) => _values.ContainsKey(prefix); 

    public ValueProviderResult GetValue(string key)
    {
        return _values.TryGetValue(key, out object value)
            ? new ValueProviderResult(Convert.ToString(value))
            : ValueProviderResult.None;
    }
}

Startup.cs

中注册JsonValueProviderFactory
services
    .AddMvc(options =>
    {
        options.ValueProviderFactories.Add(new JsonValueProviderFactory());
        //...
    });

备注

此实现仅支持普通值而不支持复杂对象,需要进行一些测试。如果您将复杂对象绑定为操作参数,请 not 忘记指定 FromBody 属性,以便发生默认模型绑定并且此值提供程序不会破坏任何内容。