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 Binding和Formatters。(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>();
}
调试结果
-
application/x-www-form-urlencoded 方式
2.application/json 方式
参考文章
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