使用WPF来做项目时,服务器端用 web api后,自然得面对着怎样调用web api的问题,如果没有良好的将访问web api功能进行封装的话,可能会写很多的冗余代码;在刚开始搭建这个框架的时候,也在考虑这个问题,当时还想着将祖传的访问web api的代码翻出来用用,结果还是做罢,一是懒得翻了,二来是代码也不算简洁,因此就懒得拿出来丢人现眼了。
WPF调用Web API
如果没有将访问web api做良好封装的话,WPF调用Web API基本上代码写起来是非常的不舒服的,就像下面冗余的调用方式一样,参数传得多是其一,其二是处理方式拿到项目中来就有点非正式场合使用了,尽管可以实现功能,当然如果您对代码要求不高的话,其实,随便咋整都行。
冗余的调用方式
WPF想要调用web api,经常可以看到的代码会是这样写的:
public T CallWebAPi<T>(string userName, string password, Uri url, out bool isSuccessStatusCode) { T result = default(T); using (HttpClient client = new HttpClient()) { client.BaseAddress = url; client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String( System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password)))); HttpResponseMessage response = client.GetAsync(url).Result; isSuccessStatusCode = response.IsSuccessStatusCode; var JavaScriptSerializer = new JavaScriptSerializer(); if (isSuccessStatusCode) { var dataobj = response.Content.ReadAsStringAsync(); result = JavaScriptSerializer.Deserialize<T>(dataobj.Result); } else if (Convert.ToString(response.StatusCode) != "InternalServerError") { result = JavaScriptSerializer.Deserialize<T>("{ \"APIMessage\":\"" + response.ReasonPhrase + "\" }"); } else { result = JavaScriptSerializer.Deserialize<T>("{ \"APIMessage\":\"InternalServerError\" }"); } } return result; }
代码来源:https://stackoverflow.com/questions/18631091/how-to-call-web-api-in-wpf-4-0
这里也给大伙展示一下早前项目中的访问web api是怎样的一种方式。
public static class HttpHelper { public static string BaseURI{get;set;} public static string SessionId { get; set; } private static readonly Encoding Encoding = Encoding.GetEncoding("UTF-8"); private static readonly int Timeout = 100000; private static readonly string ContentTypePost = "application/json"; private static readonly string ContentTypePost2 = "application/x-www-form-urlencoded"; /// <summary> /// 調用post請求接口 /// </summary> /// <param name="apiName">接口名稱</param> /// <param name="data">參數格式(JOSN)</param> /// <param name="isParameter"></param> /// <returns></returns> async public static Task<string> PostAsyc(string apiName, string data, bool isParameter = false) { return await Task.Run(() =>Send(apiName, data, isParameter)); } /// <summary> ///調用post請求接口 /// </summary> /// <param name="apiName">接口名稱</param> /// <param name="data">參數格式</param> /// <param name="isParameter"></param> /// <returns></returns> public static string Post(string apiName, string data, bool isParameter = false) { return Send(apiName, data, isParameter); } private static string Send(string apiName, string data, bool isParameter) { string result = null; try { using (HttpWebResponse response = GetResponse(apiName, data, isParameter)) { if (string.IsNullOrEmpty(SessionId)) { SessionId = response.Headers.Get("Set-Cookie"); } Stream stream = response.GetResponseStream(); if (stream != null) { StreamReader reader = new StreamReader(stream, Encoding); result = reader.ReadToEnd(); } } return result; } catch (Exception e) { var ret = new ResultJsonBase() { Succeeded = false, Message = e.Message, Data= "[]" }; return JsonHelper.SerializeObject(ret); } } private static HttpWebResponse GetResponse(string apiName, string data, bool isParameter) { StringBuilder baseURL = new StringBuilder(BaseURI); baseURL.Append(apiName); string contentType = ContentTypePost; if (isParameter) { contentType = ContentTypePost2; if (!string.IsNullOrWhiteSpace(data)) { data = data.ToLower(); JObject urlParam = (JObject)JsonConvert.DeserializeObject(data); baseURL.Append("?"); var listParam = urlParam.Properties().Select(p => { return string.Format("{0}={1}", p.Name, p.Value); }); string param = String.Join("&", listParam); baseURL.Append(param); } } HttpWebRequest request = (HttpWebRequest)WebRequest.Create(baseURL.ToString()); request.Method = "POST"; request.UserAgent = "Mozilla/5.0"; request.Timeout = Timeout; request.ContentType = contentType; request.ContentLength = 0; object accessToken = MemoryCacheManager.Get(UserContext.AccessToken); if (accessToken != null) { //token格式,Header裏面設置 request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken.ToString())); } if (!string.IsNullOrEmpty(SessionId)) { request.CookieContainer = new CookieContainer(); request.CookieContainer.SetCookies(new Uri(baseURL.ToString()), SessionId); } if (!isParameter) { if (!string.IsNullOrEmpty(data)) { byte[] bytes = Encoding.GetBytes(data); request.ContentLength = bytes.Length; request.GetRequestStream().Write(bytes, 0, bytes.Length); } else { request.ContentLength = 0; } } try { return (HttpWebResponse)request.GetResponse(); } catch (Exception ex) { throw ex; } } /// <summary> /// 測試服務器是否處於連接狀態 /// </summary> /// <param name="endPoint">端口號</param> /// <param name="ip">ip地址</param> async public static Task<bool> ConnectTest(string ip, int endPoint) { var client = new HttpClient(); client.BaseAddress = new Uri($"http://{ip}:{endPoint}"); client.Timeout = TimeSpan.FromSeconds(5); try { var response = await client.PostAsync("/api/Login/TestConnect", null); return response.StatusCode == HttpStatusCode.OK; } catch { return false; } } /// <summary> /// 通過post方式調用通用上傳文件接口 /// </summary> /// <param name="apiName">接口名稱</param> /// <param name="para">參數格式(json格式)</param> /// <param name="fileNames">文件路徑</param> /// <returns></returns> public async static Task<string> PostFilesAsync(string apiName, string para, List<string> fileNames) { if (fileNames != null && fileNames.Count > 0) { var baseURL = new StringBuilder(); baseURL.Append(BaseURI); baseURL.Append(apiName); if (!string.IsNullOrWhiteSpace(para)) { JObject urlParam = (JObject)JsonConvert.DeserializeObject(para); baseURL.Append("?"); var listParam = urlParam.Properties().Select(p => { return string.Format("{0}={1}", p.Name, p.Value); }); string param = String.Join("&", listParam); baseURL.Append(param); } var client = new RestClient(baseURL.ToString()); client.Timeout = -1; var request = new RestRequest(Method.POST); //設置token和添加上傳文件參數 object accessToken = MemoryCacheManager.Get(UserContext.AccessToken); if (accessToken != null) request.AddHeader("Authorization", string.Format("Bearer {0}", accessToken.ToString())); foreach (var item in fileNames) request.AddFile("files", item); IRestResponse response = await client.ExecuteAsync(request); return response.Content; } return null; } }
以上代码呢,可能就是常见的一种封装了,简单的弄个 XXXHelper就行了,而在需要调用的地方呢,还得这样写才行:
string jsonResult = HttpHelper.Post(url, strQuery, true); var Result = JsonHelper.AnalyzeJsonString(jsonResult);
上面的url参数,是web api接口的URL,是这样定义的:
public class ApiNames { /// <summary> /// 通用上传附件接口 /// </summary> public const string FileUpload = "/api/Common/Upload"; /// <summary> /// 获取病人信息 /// </summary> public const string GetPatientInfo = "/api/Common/GetPatientInfo"; }
这可能是我们都会这样干的一种方式来编写web api接口的方式,上面的方式还得处理收到响应内容再反序列化成需要的对象,相信经常写这种代码的猿呢,是非常苦恼的。
早点下班更好写代码的方式
程序猿,早点下班何乐而不为呢?更多的时间做自己想做的事情,如建个自己的网站来赚取被动收入不是更好吗?
在JHRS框架中,WPF访问web api是引入了refit来解决冗余代码和优雅的问题,因此在实际项目中,您要做的事情就2个。
第一个:编写与web api对应的接口
public interface IGitHubApi { [Get("/users/{user}")] Task<User> GetUser(string user); } 来自github refit示例接口
第二个:调用它完成你的目的
var gitHubApi = RestService.For<IGitHubApi>("https://jhrs.com"); var octocat = await gitHubApi.GetUser("octocat");
从上面的例子看出,WPF调用Web API是不是很简单了呢?但第一个编写web api接口这个事情,在大型项目中,接口成百上千个,手工编写也是一个体力活;因此还得想些招来减少干这些体力活,因此在框架中,顺手编写了一个反向解析swagger的工具,尽管可能存在bug,但修复起来也不难,因为这个工具在我们的项目中,已经被团队成员使用了。
是不是很省事了呢?有些体力活就该交给工具来给你完成。
写在最后
本篇介绍完了在真实项目中经常会写的高频代码WPF调用Web API的封装理念,以及我的一些懒人方法,最终的目的说的高大上呢就是为了提高工作效率,说的实在点呢,就是为了早点下班,不要996这种生活。
下一篇会介绍客户端是怎样设计入口项目的,即介绍一下JHRS.Shell这个入口项目库里面的代码上的一些事情,以及为什么这样做的。