|
| 1 | +# 适配畸形接口 |
| 2 | + |
| 3 | +在实际应用场景中,常常会遇到一些设计不标准的畸形接口,主要是早期还没有restful概念时期的接口,我们要区分分析这些接口,包装为友好的客户端调用接口。 |
| 4 | + |
| 5 | +## 不友好的参数名别名 |
| 6 | + |
| 7 | +例如服务器要求一个Query参数的名字为`field-Name`,这个是c#关键字或变量命名不允许的,我们可以使用`[AliasAsAttribute]`来达到这个要求: |
| 8 | + |
| 9 | +```csharp |
| 10 | +public interface IDeformedApi |
| 11 | +{ |
| 12 | + [HttpGet("api/users")] |
| 13 | + ITask<string> GetAsync([AliasAs("field-Name")] string fieldName); |
| 14 | +} |
| 15 | +``` |
| 16 | + |
| 17 | +然后最终请求uri变为api/users/?field-name=`fileNameValue` |
| 18 | + |
| 19 | +## Form的某个字段为json文本 |
| 20 | + |
| 21 | +字段 | 值 |
| 22 | +---|--- |
| 23 | +field1 | someValue |
| 24 | +field2 | {"name":"sb","age":18} |
| 25 | + |
| 26 | +对应强类型模型是 |
| 27 | + |
| 28 | +```csharp |
| 29 | +class Field2 |
| 30 | +{ |
| 31 | + public string Name {get; set;} |
| 32 | + |
| 33 | + public int Age {get; set;} |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +常规下我们得把field2的实例json序列化得到json文本,然后赋值给field2这个string属性,使用[JsonFormField]特性可以轻松帮我们自动完成Field2类型的json序列化并将结果字符串作为表单的一个字段。 |
| 38 | + |
| 39 | +```csharp |
| 40 | +public interface IDeformedApi |
| 41 | +{ |
| 42 | + Task PostAsync([FormField] string field1, [JsonFormField] Field2 field2) |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +## Form提交嵌套的模型 |
| 47 | + |
| 48 | +字段 | 值 |
| 49 | +---|---| |
| 50 | +|filed1 |someValue| |
| 51 | +|field2.name | sb| |
| 52 | +|field2.age | 18| |
| 53 | + |
| 54 | +其对应的json格式为 |
| 55 | + |
| 56 | +```json |
| 57 | +{ |
| 58 | + "field1" : "someValue", |
| 59 | + "filed2" : { |
| 60 | + "name" : "sb", |
| 61 | + "age" : 18 |
| 62 | + } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +合理情况下,对于复杂嵌套结构的数据模型,应当使用applicaiton/json,但接口要求必须使用Form提交,我可以配置KeyValueSerializeOptions来达到这个格式要求: |
| 67 | + |
| 68 | +```csharp |
| 69 | +services.AddHttpApi<IDeformedApi>(o => |
| 70 | +{ |
| 71 | + o.KeyValueSerializeOptions.KeyNamingStyle = KeyNamingStyle.FullName; |
| 72 | +}); |
| 73 | +``` |
| 74 | + |
| 75 | +## 响应未指明ContentType |
| 76 | + |
| 77 | +明明响应的内容肉眼看上是json内容,但服务响应头里没有ContentType告诉客户端这内容是json,这好比客户端使用Form或json提交时就不在请求头告诉服务器内容格式是什么,而是让服务器猜测一样的道理。 |
| 78 | + |
| 79 | +解决办法是在Interface或Method声明`[JsonReturn]`特性,并设置其EnsureMatchAcceptContentType属性为false,表示ContentType不是期望值匹配也要处理。 |
| 80 | + |
| 81 | +```csharp |
| 82 | +[JsonReturn(EnsureMatchAcceptContentType = false)] |
| 83 | +public interface IDeformedApi |
| 84 | +{ |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +## 类签名参数或apikey参数 |
| 89 | + |
| 90 | +例如每个请求的url额外的动态添加一个叫sign的参数,这个sign可能和请求参数值有关联,每次都需要计算。 |
| 91 | + |
| 92 | +我们可以自定义ApiFilterAttribute来实现自己的sign功能,然后把自定义Filter声明到Interface或Method即可 |
| 93 | + |
| 94 | +```csharp |
| 95 | +class SignFilterAttribute : ApiFilterAttribute |
| 96 | +{ |
| 97 | + public override Task OnRequestAsync(ApiRequestContext context) |
| 98 | + { |
| 99 | + var signService = context.HttpContext.ServiceProvider.GetService<SignService>(); |
| 100 | + var sign = signService.SignValue(DateTime.Now); |
| 101 | + context.HttpContext.RequestMessage.AddUrlQuery("sign", sign); |
| 102 | + return Task.CompletedTask; |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +[SignFilter] |
| 107 | +public interface IDeformedApi |
| 108 | +{ |
| 109 | + ... |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +## 表单字段排序 |
| 114 | + |
| 115 | +不知道是哪门公司起的所谓的“签名算法”,往往要字段排序等。 |
| 116 | + |
| 117 | +```csharp |
| 118 | +class SortedFormContentAttribute : FormContentAttribute |
| 119 | +{ |
| 120 | + protected override IEnumerable<KeyValue> SerializeToKeyValues(ApiParameterContext context) |
| 121 | + { |
| 122 | + 这里可以排序、加上其它衍生字段等 |
| 123 | + return base.SerializeToKeyValues(context).OrderBy(item => item.Key); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +public interface IDeformedApi |
| 128 | +{ |
| 129 | + [HttpGet("/path")] |
| 130 | + Task<HttpResponseMessage> PostAsync([SortedFormContent] Model model); |
| 131 | +} |
| 132 | +``` |
0 commit comments