6.1 C# 请求Web API的方式
前端调用有Form表单提交,ajax提交,ajax一般是用Jquery的简化写法,在这里不再过多介绍; 后端调用大约有这些:WebCient、WebRequest、Httpclient、WebapiClient,重点探讨Get和Post请求,Put和Delete请求用较少。
下面先介绍Get和Post的基本写法,最后再封装一下,便于调用。
先编写几个服务器端的接口方法,便于调用。
[HttpGet] public string CheckLogin(string userName, string pwd) { if (userName == "admin" && pwd == "123456") { return "ok"; } else { return "error"; } } [HttpPost] public string Register([FromBody]LoginModel model) { if (model.userName == "admin" && model.pwd == "123456") { return "ok"; } else { return "error"; } } [HttpPost] public string Register2([FromBody]dynamic model) { if (model.userName == "admin" && model.pwd == "123456") { return "ok"; } else { return "error"; } } |
为了方便下面的测试,给上述三个地址进行命名描述和序列化方法的初始化
6.1.1 WebClient
1. Get 请求
WebClient wc = new WebClient(); string url = url1; wc.Encoding = Encoding.UTF8; string result = wc.DownloadString(url); Console.WriteLine(result); Console.ReadKey(); |
2. Post的表单提交方式
WebClient wc = new WebClient(); string url = url3; wc.Encoding = Encoding.UTF8; //也可以向表头中添加一些其他东西 wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); string result = wc.UploadString(url, "userName=admin&pwd=123456"); Console.WriteLine(result); Console.ReadKey(); |
3. Post的JSON提交格式
var user = new { userName = "admin", pwd = "123456" }; WebClient wc = new WebClient(); string url = url3; wc.Encoding = Encoding.UTF8; wc.Headers.Add("Content-Type", "application/json"); string result = wc.UploadString(url, jss.Serialize(user)); Console.WriteLine(result); Console.ReadKey(); |
6.1.2 WebRequest
1. Get请求
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url1); request.Timeout = 30 * 1000; request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"; request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; string result = ""; using (var res = request.GetResponse() as HttpWebResponse) { if (res.StatusCode == HttpStatusCode.OK) { StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8); result = reader.ReadToEnd(); } } Console.WriteLine(result); Console.ReadKey(); |
2. Post的表单提交方式
var postData = "userName=admin&pwd=123456"; var request = HttpWebRequest.Create(url2) as HttpWebRequest; request.Timeout = 30 * 1000;//设置30s的超时 request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; byte[] data = Encoding.UTF8.GetBytes(postData); request.ContentLength = data.Length; Stream postStream = request.GetRequestStream(); postStream.Write(data, 0, data.Length); postStream.Close(); string result = ""; using (var res = request.GetResponse() as HttpWebResponse) { if (res.StatusCode == HttpStatusCode.OK) { StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8); result = reader.ReadToEnd(); } } Console.WriteLine(result); Console.ReadKey(); |
3. Post的JSON提交格式
var user = new { userName = "admin", pwd = "123456" }; var postData = jss.Serialize(user); var request = HttpWebRequest.Create(url2) as HttpWebRequest; request.Timeout = 30 * 1000; //设置30s的超时 request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"; request.ContentType = "application/json"; request.Method = "POST"; byte[] data = Encoding.UTF8.GetBytes(postData); request.ContentLength = data.Length; Stream postStream = request.GetRequestStream(); postStream.Write(data, 0, data.Length); postStream.Close(); string result = ""; using (var res = request.GetResponse() as HttpWebResponse) { if (res.StatusCode == HttpStatusCode.OK) { StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8); result = reader.ReadToEnd(); } } Console.WriteLine(result); Console.ReadKey(); |
6.1.3 HttpClient
1. Get请求
var handler = new HttpClientHandler(); using (var http = new HttpClient(handler)) { var response = http.GetAsync(url1).Result; //获取Http的状态值 //Console.WriteLine(response.StatusCode); string result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); Console.ReadKey(); } |
2. Post的表单提交方式
var handler = new HttpClientHandler(); using (var http = new HttpClient(handler)) { var content = new StringContent("userName=admin&pwd=123456", Encoding.UTF8, "application/x-www-form-urlencoded"); var response = http.PostAsync(url2, content).Result; //获取Http的状态值 //Console.WriteLine(response.StatusCode); string result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); Console.ReadKey(); } |
3. Post的JSON提交格式
var user = new { userName = "admin", pwd = "123456" }; var handler = new HttpClientHandler(); using (var http = new HttpClient(handler)) { var content = new StringContent(jss.Serialize(user), Encoding.UTF8, "application/json"); var response = http.PostAsync(url3, content).Result; //获取Http的状态值 //Console.WriteLine(response.StatusCode); string result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); Console.ReadKey(); } |
以上代码均是官方的给出的标准写法,但存在很严重的问题,当请求量大的时候,会存在不能释放的问题 。
目前使用的比较多的是HttpClient,它适合用于多次请求操作。将HttpClient做成单例的,不用Using,全局只有一个,来解决tcp连接不能释放的问题。此外,HttpClient提供了异步支持,可以轻松配合async await 实现异步请求。
工厂类,单例。
/// <summary> /// 将HttpClient做成单例的,不用Using,全局只有一个 /// 来解决tcp连接不能释放的问题 /// </summary> public class HttpClientFactory { private static HttpClient _httpClient = null; /// <summary> /// 静态的构造函数:只能有一个,且是无参数的 /// 由CLR保证,只有在程序第一次使用该类之前被调用,而且只能调用一次 /// 说明: keep-alive关键字可以理解为一个长链接,超时时间也可以在上面进行设置,例如10秒的超时时间,当然并发量太大,这个10秒应该会抛弃很多请求 /// 发送请求的代码没有了using,即这个httpclient不会被手动dispose,而是由系统控制它,当然你的程序重启时,这也就被回收了。 /// </summary> static HttpClientFactory() { _httpClient = new HttpClient(new HttpClientHandler()); _httpClient.Timeout = new TimeSpan(0, 0, 10); _httpClient.DefaultRequestHeaders.Connection.Add("keep-alive"); } /// <summary> /// 对外开放接口 /// </summary> /// <returns></returns> public static HttpClient GetHttpClient() { return _httpClient; } } |
对HttpClient的封装
public class HttpClientHelper { /// <summary> /// HttpClient的Get请求 /// </summary> ///<param name="url">请求地址,含拼接数据 </param> /// <returns></returns> public static string Get(string url) { var http = HttpClientFactory.GetHttpClient(); var response1 = http.GetAsync(url).Result; return response1.Content.ReadAsStringAsync().Result; } /// <summary> /// HttpClient的Post请求 /// 表单提交模式[application/x-www-form-urlencoded] /// </summary> /// <param name="url">请求地址,单纯的地址,没有数据拼接</param> /// <param name="data">请求数据,格式为:"userName=admin&pwd=123456"</param> /// <returns></returns> public static string PostForm(string url, string data) { var http = HttpClientFactory.GetHttpClient(); var content = new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded"); var response = http.PostAsync(url, content).Result; return response.Content.ReadAsStringAsync().Result; } #endregion /// <summary> /// HttpClient的Post请求 /// Json提交模式[application/json] /// </summary> /// <param name="url">请求地址,单纯的地址,没有数据拼接</param> /// <param name="data">请求数据,格式为(Json)对象、或者类对象 </param> /// <returns></returns> public static string PostJSON(string url, object data) {
var http = HttpClientFactory.GetHttpClient(); var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"); var response = http.PostAsync(url, content).Result; return response.Content.ReadAsStringAsync().Result; } #endregion } |
6.2 多版本控制
6.2.1 多版本管理的概念
Android 、IOS等 App 存在着多版本客户端共存的问题:App 最新版已经升级到了5.0 了,但是有的用户手机上还运行着 4.8、3.9 甚至2.2 版本的 App,由于早期没有内置升级机制、用户不会升级、用户拒绝升级等原因,造成这些旧版本 App 也在运行。开发新版本 App 的时候,要给接口增加新的功能或者修改以前接口的规范,会造成旧版本App 无法使用,因此在一定情况下会“保留旧接口的运行、新功能用新接口”,这样就会存在多版本接口共存的问题。
通常的做法是:旧版接口做一个代码分支,除了进行 bug 修改外,旧版本接口不再做改动,新接口代码继续演化升级。在客户端请求的时候带着要请求的接口版本号,在服务器端选择合适的版本代码进行处理。
6.2.2 多版本解决方案
- 不同的版本使用不同的域名:v1.api.ypf.com、v2.api.ypf.com、v3…… (最佳方案)
- 在Url,报文头等中带不同的版本信息,用Nginx等做反向代理服务,然后将 http://api.ypf.com/api/v1/User/1和http://api.ypf.com/api/v2/User/1 转到不同的服务器处理。
- 多个版本的 Controller共处在一个项目中,然 后使 用 [RoutePrefix] 特性来进行区分,这种方案Controller的名字不能一样,如下:
4. 如果我想在Controller文件夹中新建多个版本文件夹,如:v1、v2、v3,每个文件夹中存放的控制器名称相同,比如都叫PersonController,不同文件夹下代表不同版本,这个时候会有一个很尴尬的问题,没法请求,识别不了,这个时候就需要重写系统默认的机制,IHttpControllerSelector 根据 “报文头”或者“请求路径”等选择不同的 Controller 执行。