背景
我们有一些对外公共接口,为很多不同的调用方提供服务,经常会有调用方因为使用了错误的协议、数据提交方式而导致提交失败,所以我们决定整合一下这个接口,使接口同时支持GET、POST请求,并且数据提交方式支持application/json、application/x-www-form-urlencoded、URL参数。
要解决的问题
- 同时支持
GET、POST
- 同时支持
application/json、application/x-www-form-urlencoded、URL参数
- 因为接口比较多,所以绑定的方法必须能够复用
具体实现
本文基于.NET5实现
- 要实现同时支持
GET、POST很简单,直接为接口标识需要的协议即可
1
2
3
4
5
|
[HttpGet]
[HttpPost]
public JsonResult Test()
{
}
|
- 要支持不同形式的参数格式,那么
.NET自带的[FromQuery]、[FromForm]特性就不能满足要求了。这时需要使用ModelBinder来实现一个自定义绑定数据的方法
ModelBinderAttribute可以指定要用于绑定的模型名称或类型的属性 IModelBinder
基础定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[ModelBinder(typeof(MyBinder))]
public class MyVO
{
public string Name { get; set; }
public int Age { get; set; }
}
//实现IModelBinder接口以完成数据绑定
public class MyBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
return Task.Run(() =>
{
//具体的解析数据
var vo = new MyVO { Name = "张三", Age = 23 };
bindingContext.Result = ModelBindingResult.Success(vo);
//解析失败
//bindingContext.Result = ModelBindingResult.Failed();
});
}
}
|
解析application/json示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public Task BindModelAsync(ModelBindingContext bindingContext)
{
return Task.Run(() =>
{
var request = bindingContext.HttpContext.Request;
Int64? len = request.ContentLength;
if (!len.HasValue)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
byte[] buffer = new byte[len.Value];
//.NET5默认必须使用异步的方式读取流
int readLength = request.Body.ReadAsync(buffer, 0, (int)len.Value).Result;
string json = Encoding.UTF8.GetString(buffer);
var obj = System.Text.Json.JsonSerializer.Deserialize<MyVO>(json);
bindingContext.Result = ModelBindingResult.Success(obj);
});
}
|
解析application/x-www-form-urlencoded、URL参数示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public Task BindModelAsync(ModelBindingContext bindingContext)
{
return Task.Run(() =>
{
var vo = new MyVO();
//application/x-www-form-urlencoded格式
vo.Name = bindingContext.HttpContext.Request.Form["Name"].ToString();
vo.Age = Convert.ToInt32(bindingContext.HttpContext.Request.Form["Age"].ToString());
//URL参数格式
//vo.Name = bindingContext.HttpContext.Request.Query["Name"].ToString();
//vo.Age = Convert.ToInt32(bindingContext.HttpContext.Request.Query["Age"].ToString());
bindingContext.Result = ModelBindingResult.Success(vo);
});
}
|
- 要支持解析不同的模型,这里就比较简单了,直接使用泛型即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
[ModelBinder(typeof(MyBinder<MyVO>))]
public class MyVO
{
public string Name { get; set; }
public int Age { get; set; }
}
//实现IModelBinder接口以完成数据绑定
public class MyBinder<T> : IModelBinder where T : new()
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
return Task.Run(() =>
{
var t = new T();//因为是为模型赋值,所以必须实例化对象,泛型需要实例化约束
var request = bindingContext.HttpContext.Request;
if (request.ContentType == "application/json")
{
Int64? len = request.ContentLength;
if (!len.HasValue)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
byte[] buffer = new byte[len.Value];
string data = "";
var l = request.Body.ReadAsync(buffer, 0, (int)len.Value).Result;
string json = Encoding.UTF8.GetString(buffer);
//Json格式的解析比较简单,直接传递一个泛型T就完事了
t = System.Text.Json.JsonSerializer.Deserialize<T>(json);
bindingContext.Result = ModelBindingResult.Success(t);
return;
}
//form和url参数需要使用反射来获取不同的字段,然后再获取对应的参数值
foreach (PropertyInfo p in t.GetType().GetProperties())
{
string fieldName = p.Name;
object tmpValue = "";
switch (request.ContentType)
{
case "application/x-www-form-urlencoded":
tmpValue = request.Form[fieldName].ToString();
break;
default:
tmpValue = request.Query[fieldName].ToString();
break;
}
var property = t.GetType().GetProperty(fieldName);
property?.SetValue(t, Convert.ChangeType(tmpValue, property.PropertyType));
}
bindingContext.Result = ModelBindingResult.Success(t);
});
}
}
|
因为指定了自定义绑定方式,所以在具体的接口方法定义时就不需要再指定绑定方式了
1
2
3
4
5
|
[HttpGet]
[HttpPost]
public JsonResult Test(MyVO myVo)
{
}
|
好了,大功告成,收工~~👊👊
