zoukankan      html  css  js  c++  java
  • WebApi 参数绑定踩坑

    起因

    ​ 项目开始阶段,我设计是使用WebApi开发移动端接口,因为之前对MVC很熟悉,而且是Rest风格。
    但发现WebApi也有些不同,标注[FromBody]的参数只接收第一个,请求body中的所有参数都会反序列化到这个参数中,例如:

    [HttpPost]
    public HttpResponseMessage test1([FromBody]Model requestData,[FromBody]Model model)
    {
        //model参数为null
        return CommonHandler.JSONResult("");
    }
    

    ​ 动手开发时发现,针对post的请求[FromBody]参数,针对复杂类型需要创建相应的类型(使用字典类型无效)。为了减少创建太多类,所以想要实现用String接收JSON字符串,手动反序列化(真的很扯淡)。

    $.ajax({
        url: "/api/test",
        type: "Post",
        data: {"":'{ "id": 1, "name": "adc" }'},
        success: function (data) {
            
        }
    });
    
    public HttpResponseMessage test1([FromBody]string requestData){
    	Dictionary<string, string> dictData = JSONHelper.JSONToObject<Dictionary<string, string>>(requestData);
        //dictData["id"],dictData["name"]
        return CommonHandler.JSONResult("");
    }
    

    问题

    ​ 随着功能的增多,发现使用字典类型接收参数,代码冗余且排查代码无法知道移动端传参是否有漏传字段,所以新加的接口还是创建相应的参数类型。但是已发布的接口要保证正常运行,要保证 data: {"":'{ "id": 1, "name": "adc" }'}, 键为空字符串,content-type=application/x-www-form-urlencoded 的方式能正常使用,所以要自定义参数绑定规则

    技术点

    ​ 参数绑定有两种技术:Model BindingFormatters。(MVC 只有Model Binding)。 实际上,WebAPI使用model binding读取查询字符串(query string)内容进行参数绑定,使用Formatters读取主体内容(body content)进行参数的绑定。

    Model Binding 方式

    ​ 此方式并不能接收 body content,无法解决application/json,只做了解学习

    ​ 在Global.asax.cs 文件中,Application_Start()事件,定义了程序配置接口 找到 GlobalConfiguration.Configure(WebApiConfig.Register);
    我们要在WebApiConfig.Register 方法中做修改

    //WebApiConfig.cs
    public static void Register(HttpConfiguration config)
    {
        // Web API 配置和服务
        // 将 Web API 配置为仅使用不记名令牌身份验证。
        //config.SuppressDefaultHostAuthentication();
        //config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        config.Filters.Add(new QXZ.Hub.Interface.Filter.NotImplExceptionAttribute());
        // Web API 路由
        config.MapHttpAttributeRoutes();
    
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}",
            defaults: new { id = RouteParameter.Optional }
        );
        
        
        /////新增,针对自定义类型进行绑定
        config.BindParameter(typeof(SelfClass),new SelfBindModel());
    }
    
    public class SelfBindModel:IModelBinder
    {
        public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            //类型筛选
            if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName) || !MutableObjectModelBinder.CanBindType(bindingContext.ModelType))
                return false;
            //1. 创建空对象
            bindingContext.ModelMetadata.Model = Activator.CreateInstance(bindingContext.ModelType);
            //2. 创建ComplexModelDto对象
            ComplexModelDto complexModelDto = new ComplexModelDto(bindingContext.ModelMetadata,bindingContext.PropertyMetadata);
            //3. 进入绑定流程
            this.ProcessDto(actionContext, bindingContext, complexModelDto);
            return true;
        }
    
        internal void ProcessDto(HttpActionContext actionContext, ModelBindingContext bindingContext, ComplexModelDto dto)
        {
            //创建子ModelBindingContext
            var subContext = new ModelBindingContext(bindingContext)
            {
                ModelName = bindingContext.ModelName,
                ModelMetadata = GetMetadata(typeof(ComplexModelDto))
            };
            //调用ComplexModelDtoModelBinder 对ComplexModelDto进行绑定
            actionContext.Bind(subContext);
    
            //对复杂类型的属性进行绑定
            foreach (var result in (IEnumerable<KeyValuePair<ModelMetadata, ComplexModelDtoResult>>) dto.Results)
            {
                ModelMetadata key = result.Key;
                ComplexModelDtoResult dtoResult = result.Value;
                if (dtoResult != null)
                {
                    var property = bindingContext.ModelType.GetProperty(key.PropertyName);
                    this.SetProperty(actionContext, bindingContext, key, dtoResult,property);
                }
            }
        }
    }
    
    

    Formatters 方式

    Webapi 默认注入了四类Formatters

    System.Net.Http.Formatting.JsonMediaTypeFormatter
    System.Net.Http.Formatting.XmlMediaTypeFormatter
    System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter
    System.Web.Http.ModelBinding.JQueryMvcFormUrlEncodedFormatter
    

    新增自定义Formatters ,需加到这四类之前

    //WebApiConfig.cs
    public static void Register(HttpConfiguration config)
    {
        // Web API 配置和服务
        // 将 Web API 配置为仅使用不记名令牌身份验证。
        //config.SuppressDefaultHostAuthentication();
        //config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        config.Filters.Add(new QXZ.Hub.Interface.Filter.NotImplExceptionAttribute());
        // Web API 路由
        config.MapHttpAttributeRoutes();
    
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}",
            defaults: new { id = RouteParameter.Optional }
        );
        
        
        ////新增代码
        //插入首部
        config.Formatters.Insert(0, new StringMediaTypeFormatter());
    }
    
    
    //新建类型
    public class StringMediaTypeFormatter : System.Net.Http.Formatting.MediaTypeFormatter
    {
        public StringMediaTypeFormatter()
        {
        	//支持媒体类型application/json
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        }
        public override bool CanReadType(Type type)
        {
        	//针对参数为String 类型的Action
            return type.Equals("".GetType());
        }
    
        public override bool CanWriteType(Type type)
        {
        	//不写入输出流,设为false
            return false;
        }
    
        public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream,
            HttpContent content, IFormatterLogger formatterLogger)
        {
            using (var sr = new StreamReader(readStream))
            {
                return await sr.ReadToEndAsync();
            }
        }
    }
    
    [HttpPost]
    public IEnumerable<string> test([FromBody]string requestData)
    {
        IList<string> formatters = new List<string>();
    
        foreach (var item in GlobalConfiguration.Configuration.Formatters)
        {
            formatters.Add(item.ToString());
        }
    
        return formatters.AsEnumerable<string>();
    }
    

    调试结果

    1. application/x-www-form-urlencoded 方式
      formdata1

      formdata2

    2.application/json 方式
    applicationJson1

    applicationJson2

    applicationJson3

    applicationJson4

    参考文章

    https://docs.microsoft.com/en-us/previous-versions/aspnet/hh834220(v%3Dvs.108)

    https://docs.microsoft.com/en-us/previous-versions/aspnet/hh834517(v%3Dvs.108)

    https://www.cnblogs.com/artech/archive/2012/05/21/model-binder-provision.html

    https://www.cnblogs.com/artech/archive/2012/05/23/default-model-binding-01.html

  • 相关阅读:
    读书笔记
    STL 笔记
    Centos8如何配置网桥
    命令集合
    shared_ptr给管理的对象定制析沟函数
    share_ptr指向的对象的析构动作在创建的时候被捕获
    优秀博客
    字符串单词翻转
    带权图的最短路径算法(Dijkstra,Floyd,Bellman_ford)
    生产者与消费者 c++实现
  • 原文地址:https://www.cnblogs.com/wycm/p/13644761.html
Copyright © 2011-2022 走看看