WebApiClient基础

本章节为了方便读者的理解,相关例子将使用HttpApiClient静态类来创建http接口的代理类,但在生产环境中,使用HttpApiFactory静态来创建http接口的代理类更合理,也是非常有必要的。

1. GET/HEAD请求

1.1 Get请求简单例子

public interface IMyWebApi : IHttpApi
{
    // GET http://www.mywebapi.com/webapi/user?account=laojiu
    [HttpGet("http://www.mywebapi.com/webapi/user")]
    ITask<HttpResponseMessage> GetUserByAccountAsync(string account);
}

var client = HttpApiClient.Create<IMyWebApi>();
var response = await client.GetUserByAccountAsync("laojiu");

1.2 使用HttpHost特性

[HttpHost("http://www.mywebapi.com/")]
public interface IMyWebApi : IHttpApi
{
    // GET /webapi/user?account=laojiu
    [HttpGet("webapi/user")]
    ITask<HttpResponseMessage> GetUserByAccountAsync(string account);
}

如果接口IMyWebApi有多个方法且都指向同一服务器,可以将请求的域名抽出来放到HttpHost特性。

1.3 响应的json/xml内容转换为强类型模型

1.3.1 隐式转换为强类型模型

[HttpHost("http://www.mywebapi.com/")]
public interface IMyWebApi : IHttpApi
{
    // GET /webapi/user?account=laojiu
    [HttpGet("webapi/user")]    
    ITask<UserInfo> GetUserByAccountAsync(string account);
}

当方法的返回数据是UserInfo类型的json或xml文本,且响应的Content-Type为application/json或application/xml值时,方法的原有返回类型ITask(Of HttpResponseMessage)就可以声明为ITask(Of UserInfo)。

1.3.2 显式转换为强类型模型

[HttpHost("http://www.mywebapi.com/")]
public interface IMyWebApi : IHttpApi
{
    // GET /webapi/user?account=laojiu
    [HttpGet("webapi/user")]  
    [JsonReturn] // 指明使用Json处理返回值为UserInfo类型
    ITask<UserInfo> GetUserByAccountAsync(string account);
}

当方法的返回数据是UserInfo类型的json或xml文本,但响应的Content-Type可能不是期望的application/json或application/xml值时,就需要显式声明JsonReturn或XmlReturn特性。

2.请求URL

2.1 URL的格式

无论是GET还是POST等哪种http请求方法,都遵循如下的URL格式:

{Scheme}://{UserName}:{Password}@{Host}:{Port}{Path}{Query}{Fragment}

例如:http://account:password@www.baidu.com/path1/?p1=abc#tag

2.2 动态PATH

public interface IMyWebApi : IHttpApi
{
    // GET http://www.webapiclient.com/laojiu
    [HttpGet("http://www.webapiclient.com/{account}"]
    ITask<string> GetUserByAccountAsync(string account);
}

某些接口方法将路径的一个分段语意化,比如GET http://www.webapiclient.com/{account},这里不同的{account}代表不同账号下的个人信息,使用{参数名}声明路径,在请求前会自动从参数(或参数模型的同名属性)取值替换。

2.3 动态URL

public interface IMyWebApi : IHttpApi
{
    // GET {URL}
    [HttpGet]
    ITask<string> GetUserByAccountAsync([Url] string url);
    
    // GET {URL}?account=laojiu
    [HttpGet]
    ITask<string> GetUserByAccountAsync([Url] string url, string account);
}

如果请求URL在运行时才确定,可以将请求URL作为一个参数,使用Url特性修饰这个参数并作为第一个参数。

2.4 Query参数

2.4.1 多个query参数平铺

// GET /webapi/user?account=laojiu&password=123456
[HttpGet("webapi/user")]
ITask<UserInfo> GetUserAsync(string account, string password);

2.4.2 多个query参数合并到模型

public class LoginInfo
{
    public string Account { get; set; }
    public string Password { get; set; }         
}

// GET /webapi/user?account=laojiu&password=123456
[HttpGet("webapi/user")]
ITask<UserInfo> GetUserAsync(LoginInfo loginInfo);

2.4.3 多个query参数平铺+部分合并到模型

public class LoginInfo
{
    public string Account { get; set; }
    public string Password { get; set; }         
}

// GET /webapi/user?account=laojiu&password=123456&role=admin
[HttpGet("webapi/user")]
ITask<UserInfo> GetUserAsync(LoginInfo loginInfo, string role);

2.4.4 显式声明PathQuery特性

// GET /webapi/user?account=laojiu&password=123456&role=admin
[HttpGet("webapi/user")]
ITask<UserInfo> GetUserAsync(
    [PathQuery]LoginInfo loginInfo,
    [PathQuery]string role);

对于没有任何特性修饰的每个参数,都默认被PathQuery修饰,表示做为请求路径或请求参数处理,PathQuery特性可以设置Encoding、IgnoreWhenNull和DateTimeFormat多个属性。

3.POST/PUT/DELETE请求

3.1 使用Json或Xml提交

  • 使用XmlContent修饰强类型模型参数,表示提交xml
  • 使用JsonContent修饰强类型模型参数,表示提交json
// POST webapi/user  
// Body user的json文本
[HttpPost("webapi/user")]
ITask<UserInfo> AddUserWithJsonAsync([JsonContent] UserInfo user);

// PUT webapi/user  
// Body user的xml文本
[HttpPut("/webapi/user")]
ITask<UserInfo> UpdateUserWithXmlAsync([XmlContent] UserInfo user);

3.2 使用x-www-form-urlencoded提交

  • 使用FormContent修饰强类型模型参数
  • 使用FormField修饰简单类型参数
// POST webapi/user  
// Body Account=laojiu&Password=123456
[HttpPost("webapi/user")]
ITask<UserInfo> UpdateUserWithFormAsync(
    [FormContent] UserInfo user);

// POST webapi/user  
// Body Account=laojiu&Password=123456&fieldX=xxx
[HttpPost("webapi/user")]
ITask<UserInfo> UpdateUserWithFormAsync(
    [FormContent] UserInfo user, 
    [FormField] string fieldX);

3.3 使用multipart/form-data提交

  • 使用MulitpartContent修饰强类型模型参数
  • 使用MulitpartText修饰简单类型参数
  • 使用MulitpartFile类型作为提交的文件
// POST webapi/user  
[HttpPost("webapi/user")]
ITask<UserInfo> UpdateUserWithMulitpartAsync([MulitpartContent] UserInfo user);

// POST webapi/user  
[HttpPost("/webapi/user")]
ITask<UserInfo> UpdateUserWithMulitpartAsync(
    [MulitpartContent] UserInfo user, 
    [MulitpartText] string nickName,
    MulitpartFile file);

3.4 使用具体的HttpContent类型提交

// POST webapi/user  
// Body Account=laojiu&Password=123456
[HttpPost("webapi/user")]
ITask<UserInfo> UpdateUserWithFormAsync(
    FormUrlEncodedContent user);

// POST webapi/user  
// Body Account=laojiu&Password=123456&age=18
[HttpPost("webapi/user")]
ITask<UserInfo> UpdateUserWithFormAsync(
    FormUrlEncodedContent user,
    [FormField] int age);

如果参数是类型是HttpContent类型的子类,如StringContent、ByteArrayContent、StreamContent、FormUrlEncodedContent等等,则可以直接做为参数,但是必须放在其它参数的前面

4 PATCH请求

json patch是为客户端能够局部更新服务端已存在的资源而设计的一种标准交互,在RFC6902里有详细的介绍json patch,通俗来讲有以下几个要点: 1. 使用HTTP PATCH请求方法; 2. 请求body为描述多个opration的数据json内容; 3. 请求的Content-Type为application/json-patch+json;

4.1 WebApiClient例子

public interface IMyWebApi : IHttpApi
{
    [HttpPatch("http://www.mywebapi.com/webapi/user")]
    Task<string> PatchAsync(JsonPatchDocument<UserInfo> doc);
}


var doc = new JsonPatchDocument<UserInfo>();
doc.Replace(item => item.Account, "laojiu");
doc.Replace(item => item.Email, "laojiu@qq.com");
var client = HttpApiClient.Create<IMyWebApi>();
await client.PatchAsync(doc);

4.2 Asp.net 服务端例子

[HttpPatch]
public UserInfo Patch([FromBody] JsonPatchDocument<UserInfo> doc)
{
    // 此处user是从db查询获得
    var user = new UserInfo
    {
        Account = "_Account",
        Password = "_Password",
        Email = "_Email"
    };

    doc.ApplyTo(user);
    return user;
}

5. 参数及属性注解

这些注解特性的命名空间在WebApiClient.DataAnnotations,用于影响参数的序列化行为。

5.1 参数别名

public interface IMyWebApi : IHttpApi
{
    // GET http://www.mywebapi.com/webapi/user?_name=laojiu
    [HttpGet("http://www.mywebapi.com/webapi/user")]
    ITask<string> GetUserByAccountAsync(
        [AliasAs("_name")] string account);
}

5.2 参数模型属性注解

public class UserInfo
{
    public string Account { get; set; }

    // 别名
    [AliasAs("a_password")]
    public string Password { get; set; }

    // 时间格式,优先级最高
    [DateTimeFormat("yyyy-MM-dd")]
    [IgnoreWhenNull] // 值为null则忽略序列化
    public DateTime? BirthDay { get; set; }
    
    // 忽略序列化
    [IgnoreSerialized]
    public string Email { get; set; } 
    
    // 时间格式
    [DateTimeFormat("yyyy-MM-dd HH:mm:ss")]
    public DateTime CreateTime { get; set; }
}

6. 参数及参数属性输入验证

这些验证特性都有相同的基类ValidationAttribute,命名空间为System.ComponentModel.DataAnnotations,由netfx或corefx提供。

6.1 参数值的验证

[HttpGet("webapi/user/GetById/{id}")]
ITask<HttpResponseMessage> GetByIdAsync(
    [Required, StringLength(10)] string id);

id的参数要求必填且最大长度为10的字符串,否则抛出ValidationException的异常。

6.2 参数的属性值验证

public class UserInfo
{
    [Required]
    [StringLength(10, MinimumLength = 1)]
    public string Account { get; set; }

    [Required]
    [StringLength(10, MinimumLength = 6)]
    public string Password { get; set; }
}

[HttpPut("webapi/user/UpdateWithJson")]
ITask<UserInfo> UpdateWithJsonAsync(
    [JsonContent] UserInfo user);

当user参数不为null的情况,就会验证它的Account和Password两个属性,HttpApiConfig有个UseParameterPropertyValidate属性,设置为false就禁用验证参数的属性值。

6.3 两者同时验证

对于上节的例子,如果我们希望user参数值也不能为null,可以如下声明方法:

[HttpPut("webapi/user/UpdateWithJson")]
ITask<UserInfo> UpdateWithJsonAsync(
    [Required, JsonContent] UserInfo user);

7.特性的范围和优先级

7.1 特性的范围

有些特性比如Header,可以修饰于接口、方法和参数,使用不同的构造器和修饰于不同的地方产生的含义和结果是有点差别的:

  • 修饰接口时,表示接口下的所有方法在请求前都会添加这个请求头;
  • 修饰方法时,表示此方法在请求前添加这个请求头;
  • 修饰参数时,表示参数的值将做为请求头的值,由调用者动态传入;

7.2 特性的优先级

  • 方法级比接口级优先级高;
  • AllowMultiple为true时,方法级和接口级都生效;
  • AllowMultiple为false时,方法级的生效,接口级的无效;

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

servlet/filter/listener/interceptor区别与联系

由于最近两个月工作比较清闲,个人也比较“上进”,利用工作空余时间,也继续学习了一下,某天突然想起struts2和struts1的区别的时 候,发现 为什么st...

8120
来自专栏java一日一条

Maven 扫盲(上)

写这个maven的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于maven不是那么了解,所以就有了这个想法。这个入门篇分上下两篇。本文着重动手,用ma...

9210
来自专栏java一日一条

Maven 扫盲(下)

第一篇文章大概的介绍了一下Apache Maven以及它的下载和安装,并且运行了一个简单的示例。那么在对maven有了一点接触后,接下去的一步是要了解maven...

8210
来自专栏林德熙的博客

win10 uwp 反射

本文在h神的指导下完成。 反射是强大的好用的,我们可以添加新功能不修改之前的代码,通过使用反射得到。 本文下面和大家说如何做一个和WPF一样的反射功能,如何才能...

13320
来自专栏行者常至

Struts2.之HelloWorld简单演示

11320
来自专栏java一日一条

servlet/filter/listener/interceptor区别与联系

由于最近两个月工作比较清闲,个人也比较“上进”,利用工作空余时间,也继续学习了一下,某天突然想起struts2和struts1的区别的时 候,发现 为什么st...

11120
来自专栏行者常至

(03)Struts2_搭建环境

8010
来自专栏行者常至

(09)Struts2 请求的扩展名问题

7710
来自专栏java一日一条

面试中问的话题Spring工作原理

Spring工作原理 内部最核心的就是IOC了, 动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射 反射其实就是在...

12740
来自专栏java一日一条

在成为Java团队领导人15年后我学到了哪些?

通过运营该组织,我发现了一些规律,可能会对其他组织领导者(或者即将发起组织的人)有所帮助。我也积累了一些给演讲者的建议,并观察到许多长期会员的职业发展路径。

11810

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励