WebAPI是建立在MVC和WCF的基礎上的,原來微軟老是喜歡封裝的很多,這次終於願意將http程式設計模型的相關細節暴露給我們了。在之前的介紹中,基本上都基於.NET 4.5之後版本,其System.Net.Http程式集非常的豐富,而老版本的則相對較弱。
在WebAPI v1.0(和ASP.NET MVC4在一起的版本)很多的類和介面並不存在,同時對Task非同步程式設計(ApiController預設提供非同步執行方法)的支援還有一些欠缺(缺少不少方便的擴充套件方法),在使用時會有一些需要注意的地方,由於一些老的專案用的.NET 4.0的程式集,無法升級和使用一些新的dll,因而部分功能需要自己來考慮,本文旨在將自己遇到的一些困難分享給大家。
- 路由設定
在Global.asax檔案中需要注意WebApi的路由要先於MVC的路由進行註冊,不然會出現路由無效的情況。
前端路由地址的提供,使用@Url.HttpRouteUrl(“AddedApi”, new { controller = “SMSCenterApi”, action = “MassTexting” })來生成路由,與MVC的方式有一些差異,需要注意。
- 引數繫結
包括ModelBinder和MediaTypeFormatter兩種方式,與MVC不同(MVC均使用ModelBinder進行繫結)。前者包括針對陣列、集合、字典、簡單和複雜型別的繫結器,後者其實就是一個序列化器,預設包括3中:Json.NET的json序列化器(用的最多);DataContractSerializer和XMLSerializer用於序列化XML;最後一種解碼錶單URL,編碼主體資料。這些格式化器均在System.Net.Http.Formatting名稱空間中。
相關的特性包括:ModelBindingAttribute,預設繫結邏輯;FormUriAttribute,只從Uri獲取值;FromBodyAtrribute,使用MediaTypeFormatter媒體格式化器,也是我們在WebAPi最常用的,再次提醒一下,一定要提供contentType哦,比如”application/json”。
Tip:模型繫結常見問題,WebAPI的格式化器Formatter需要提供相應的contentType才會起作用,返回值通過dataType設定(預設為XML),一定不能忘記內容協商,需要注意內容協商,附上一個ajax呼叫的例子,我在這也吃了很大的虧,預設formatter其實做了很多事情哦。
這兒強烈提醒的是dataType表示返回值型別,contentType為請求體的型別,熊二你個二貨,內容協商是必須的,不然別人哪知道怎麼做!此外,這個的dataType=’json’最終反應到http請求體中為Accept: application/json,
這個對於你使用過濾器攔截並新建httpMessageResponse的HttpContent時非常有用,最後的例子會涉及這部分內容。
-
過濾請求
過去我們常常將一些驗證邏輯和異常處理邏輯放在Controller中,極大的增加了Controller的複雜性,完全可以通過面向切面(AOP)來處理,在.NET 4.0提供的相關基類和介面如下所示:
非同步介面和同步基類 | 用途 |
IAuthenticationFilter AuthorizationFilterAttribute | 認證過濾器可以在引數繫結發生以前執行,它們計劃過濾沒有正確認證且請求爭議操作的請求 認證過濾器先於操作過濾器執行,應用場景為驗證客戶身份,例如去Cookie或HttpHead中獲取相關驗證資訊 |
IActionFilter ActionFilterAttribute | 操作過濾器在引數繫結時發生,並封裝API操作方法呼叫之後執行,允許在排程操作之前,完成執行之後攔截。操作過濾器的目標時允許開發人員增加和替換操作的輸入值和輸出結果。如果說自定義繫結器或格式化器是用於擴充套件正常狀態下解析資料的話,那麼過濾器可以用在一些特殊情況下 |
IExceptionFilter ExceptionFilterAttribute | 當呼叫操作丟擲異常時,就會呼叫異常過濾器,可以檢查異常,並採取一些操作,例如記錄日誌、提供新的響應物件來處理異常等 |
Tip: 在MVC4中,推薦使用同步基類,在以後的版本中推薦使用非同步介面對應用程式進行擴充套件。
此外,需要注意過濾器的使用範圍,包括:全域性,在FilterConfig中新增;類級別過濾器,通過新增特性的方式;方法級別過濾器。
預設提供AuthorizeAttribute完成基礎驗證,AllowAnonymousAttribute提供匿名驗證的情況。此外還提供一個關於OData的第三方解決方案,包括可以自動支援OData查詢語法的QueryableAttribute(如$top和$filter等)。
-
其他小知識點
WebAPI的託管,包括通過System.Web.Http.WebHost.dll的IIS託管,配置物件為GlobalConfiguration;自託管的配置,通過Mocrosoft.AspNet.WebApi.Selfhost。
可以通過HttpConfiguration.Service獲取IApiExplorer服務,即全領域搜尋可用服務。
通過ITraceWriter來跟蹤應用程式,可以很方便的和ETW、Log4net、ELMAH等跟蹤服務整合。
-
簡單示例程式,包括過濾器的使用,JQuery的呼叫,請求的簡易驗籤
Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SMSCenterApiController : ApiController { [HttpPost] [CheckPermissionFilter] [ApiExceptionFilter] public WebApiResult MassTexting([FromBody]SMSCenterViewModel model) { var result = new WebApiResult { Status = WebApiResultStatus.Fail, Message = string.Empty }; int sendedNum = SMSCenterBL.Instance.MassTexting(model.SMSContent, model.PhoneList, "xionger"); result.Status = WebApiResultStatus.Success; result.Message = sendedNum.ToString(); return result; } |
Jquery呼叫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
jQuery.ajax({ type: 'POST', url: url, contentType: "application/json", dataType: 'json', data: postData, beforeSend: function (request) { request.setRequestHeader("smsToken", smsToken); }, success: function (data) { if (data.Succ == 1) { var msg = "傳送結束。成功{0} --- 共{1}"; msg = msg.format(data.Count, data.Count); alert(msg); } else { alert("傳送失敗。"); } }, error: function (data) { alert("傳送失敗。"); } }); |
CheckPremissionFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class CheckPermissionFilterAttribute : AuthorizationFilterAttribute { #region 驗證許可權 /// <summary> /// 驗證許可權 /// </summary> public override void OnAuthorization(HttpActionContext actionContext) { var token = GetHttpToken(actionContext.Request.Headers); if (SMSTokenHelper.CheckToken(token)) { base.OnAuthorization(actionContext); } else { throw new BizException("當前請求沒有訪問許可權!"); } } #endregion #region 輔助方法 /// <summary> /// 獲得請求頭中的token資訊 /// </summary> private string GetHttpToken(HttpRequestHeaders headers) { IEnumerable<string> tokenCollection; if (headers.TryGetValues(ConfigHelper.SMSCENTER_TOKEN_NAME, out tokenCollection)) { var token = tokenCollection.FirstOrDefault(); return token; } return null; } #endregion } |
SMSTokenHelper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
internal class SMSTokenHelper { public static string CreateToken(string eid) { //1.使用eid,moduleID,當前時間構建認證物件 var token = new SMSToken() { EID = eid, ModuleID = ConfigHelper.SMSCENTER_MODULE_ID, CurrentTime = DateTime.Now.ToString() }; //2.轉化為Json字串 var tokenString = JsonConvert.SerializeObject(token); //3.將json字串加密 var encryptToken = DESHelper.DESEncrypt(tokenString); return encryptToken; } public static bool CheckToken(string token) { try { //1.解密 var tokenString = DESHelper.DESDecrypt(token); //2.反序列化為物件 var smstoken = JsonConvert.DeserializeObject<SMSToken>(tokenString); //3.驗證結果 if (ConfigHelper.SMSCENTER_MODULE_ID == smstoken.ModuleID) { return true; } } catch { } return false; } private class SMSToken { public string EID { get; set; } public string ModuleID { get; set; } public string CurrentTime { get; set; } } #region 輔助類 /// <summary> /// DES加密解密 /// </summary> private class DESHelper { /// <summary> /// 獲取金鑰 /// </summary> private static string Key { get { return @"P@+#wG+Z"; } } /// <summary> /// 獲取向量 /// </summary> private static string IV { get { return @"L%n67}G/Mk@k%:~Y"; } } /// <summary> /// DES加密 /// </summary> /// <param name="plainStr">明文字串</param> /// <returns>密文</returns> public static string DESEncrypt(string plainStr) { byte[] bKey = Encoding.UTF8.GetBytes(Key); byte[] bIV = Encoding.UTF8.GetBytes(IV); byte[] byteArray = Encoding.UTF8.GetBytes(plainStr); string encrypt = null; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); try { using (MemoryStream mStream = new MemoryStream()) { using (CryptoStream cStream = new CryptoStream(mStream, des.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write)) { cStream.Write(byteArray, 0, byteArray.Length); cStream.FlushFinalBlock(); encrypt = Convert.ToBase64String(mStream.ToArray()); } } } catch { } des.Clear(); return encrypt; } /// <summary> /// DES解密 /// </summary> /// <param name="encryptStr">密文字串</param> /// <returns>明文</returns> public static string DESDecrypt(string encryptStr) { byte[] bKey = Encoding.UTF8.GetBytes(Key); byte[] bIV = Encoding.UTF8.GetBytes(IV); byte[] byteArray = Convert.FromBase64String(encryptStr); string decrypt = null; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); try { using (MemoryStream mStream = new MemoryStream()) { using (CryptoStream cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write)) { cStream.Write(byteArray, 0, byteArray.Length); cStream.FlushFinalBlock(); decrypt = Encoding.UTF8.GetString(mStream.ToArray()); } } } catch { } des.Clear(); return decrypt; } } #endregion } } |
Tip: DES加密部分借鑑博主IT合夥人文章http://www.cnblogs.com/IT-haidong/p/4856848.html
最後,補充一個在MVC4.0下的自定義ModerBinder,非常的簡單,但可以幫助實現json資料的繫結,簡化使用。當然使用JQuery的form.serialize(),將資料轉化為form提交,然後應用預設的繫結器也是ok的。以前一直form提交也沒有認真去想想form的區別,其實form是用”&“符號來連線資料的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
[AttributeUsage(AttributeTargets.Parameter)] public class FromBodyAttribute : CustomModelBinderAttribute { private static readonly ILog _logger = LogManager.GetLogger(typeof(FromBodyAttribute)); public override IModelBinder GetBinder() { return new JsonModelBinder(); } public class JsonModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (!controllerContext.HttpContext.Request.ContentType.ToLower().Contains("json")) { return null; } try { var jsonString = GetJsonString(controllerContext.HttpContext.Request.InputStream); var result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType); return result; } catch(Exception ex) { _logger.Warn(ex.Message, ex); } return null; } private string GetJsonString(Stream stream) { var jsonString = string.Empty; using (var sr = new StreamReader(stream)) { stream.Position = 0; jsonString = sr.ReadToEnd(); } return jsonString; } } } |
此外,WebAPI學習系列目錄如下,歡迎您的閱讀!
參考資料:
-
(美)加洛韋. ASP.NET MVC 4高階程式設計(第4版)[M]. 北京:清華大學出版社, 2012.