WebApi 中請求的 JSON 資料欄位作為 POST 引數傳入

OAngs發表於2021-01-13

  使用 POST 方式請求 JSON 資料到伺服器 WebAPI 介面時需要將 JSON 格式封裝成資料模型接收引數。即使引數較少,每個介面仍然需要單獨建立模型接收。下面方法實現了將 JSON 引數中的欄位作為介面引數接收。實現的並不完美,現在只支援 JSON 格式頂級結構欄位作為引數使用,且未處理複雜格式引數。

  每個接收引數都會進行 JSON 反序列化操作,故引數多的情況下建立模型接收顯然更節省資源。

  以下方法只進行過簡單測試,如有問題,還請大佬們自行解決,解決後希望能在此留言與諸位網友分享。

 

POST 提交的 JSON 資料演示:

{'Code':10000,Msg:'中文測試'}

WebApi 中獲取方式:

        [HttpPost]
        public string Post([FromJson]int Code,[FromJson]string Msg)
        {
            return "OK";
        }

FromJson 類:

    public class FromJsonAttribute : Attribute, IBindingSourceMetadata
    {
        public BindingSource BindingSource
        {
            get
            {
                return BindingSource.Custom;
            }
        }
    }

ModelBuilder 類:

    public class JsonParameterModelBinder : IModelBinder
    {
        private readonly BodyModelBinder bodyModelBinder;

        public JsonParameterModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
        {
            bodyModelBinder = new BodyModelBinder(formatters, readerFactory);
        }

        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException(nameof(bindingContext));

            var postStr = string.Empty;
            using (StreamReader sr = new StreamReader(bindingContext.HttpContext.Request.Body))
                postStr = sr.ReadToEndAsync().Result;

            var jobj = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(postStr);
            var val = jobj[bindingContext.FieldName]?.ToObject<object>();

            if (val != null)
                bindingContext.Result = ModelBindingResult.Success(Convert.ChangeType(val, bindingContext.ModelType, null));

            //將流重新寫回 Request.Body 不然第二個引數再操作時會報錯
            bindingContext.HttpContext.Request.Body = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(postStr));

            return Task.CompletedTask;
        }
    }

Provider 類:

    public class JsonParameterModelBinderProvider : IModelBinderProvider
    {
        private readonly IList<IInputFormatter> _formatters;

        public JsonParameterModelBinderProvider(IList<IInputFormatter> Formatters)
        {
            _formatters = Formatters;
        }

        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if (context.BindingInfo.BindingSource != null && context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Custom))
                return new JsonParameterModelBinder(_formatters, context.Services.GetRequiredService<IHttpRequestStreamReaderFactory>());

            return null;
        }
    }

修改 Startup.cs 的 void ConfigureServices(IServiceCollection services) 方法,增加:

            services.AddMvc(options =>
            {
                options.ModelBinderProviders.Insert(0, new JsonParameterModelBinderProvider(options.InputFormatters));
            });

插入到 0 位置,優先處理,儘量不要使用 Add 方法插入到末位,否則可能不會被處理。

以上程式碼在 .Net 5 下測試通過。其他框架版本未測試。

相關文章