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

posted @ 2020-09-10 11:39  KBright  阅读(449)  评论(0编辑  收藏  举报