ASP.NET Web API基础(06)--- 使用C# 请求Web API及多版本控制

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";

            }

        }

为了方便下面的测试,给上述三个地址进行命名描述和序列化方法的初始化

 
ASP.NET Web API基础(06)--- 使用C# 请求Web API及多版本控制

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 多版本解决方案

  1. 不同的版本使用不同的域名:v1.api.ypf.com、v2.api.ypf.com、v3……  (最佳方案)
  2. 在Url,报文头等中带不同的版本信息,用Nginx等做反向代理服务,然后将 http://api.ypf.com/api/v1/User/1和http://api.ypf.com/api/v2/User/1 转到不同的服务器处理。
  3. 多个版本的 Controller共处在一个项目中,然 后使 用 [RoutePrefix] 特性来进行区分,这种方案Controller的名字不能一样,如下:

 

ASP.NET Web API基础(06)--- 使用C# 请求Web API及多版本控制

      4. 如果我想在Controller文件夹中新建多个版本文件夹,如:v1、v2、v3,每个文件夹中存放的控制器名称相同,比如都叫PersonController,不同文件夹下代表不同版本,这个时候会有一个很尴尬的问题,没法请求,识别不了,这个时候就需要重写系统默认的机制,IHttpControllerSelector 根据 “报文头”或者“请求路径”等选择不同的 Controller 执行。

ASP.NET Web API基础(06)--- 使用C# 请求Web API及多版本控制