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 public class SMSCenterApiController : ApiController 2 { 3 [HttpPost] 4 [CheckPermissionFilter] 5 [ApiExceptionFilter] 6 public WebApiResult MassTexting([FromBody]SMSCenterViewModel model) 7 { 8 var result = new WebApiResult { Status = WebApiResultStatus.Fail, Message = string.Empty }; 9 10 int sendedNum = SMSCenterBL.Instance.MassTexting(model.SMSContent, model.PhoneList, "xionger"); 11 result.Status = WebApiResultStatus.Success; 12 result.Message = sendedNum.ToString(); 13 return result; 14 }
Jquery調用:

1 jQuery.ajax({ 2 type: 'POST', 3 url: url, 4 contentType: "application/json", 5 dataType: 'json', 6 data: postData, 7 beforeSend: function (request) { 8 request.setRequestHeader("smsToken", smsToken); 9 }, 10 success: function (data) { 11 if (data.Succ == 1) { 12 var msg = "發送結束。成功{0} --- 共{1}"; 13 msg = msg.format(data.Count, data.Count); 14 alert(msg); 15 } 16 else { 17 alert("發送失敗。"); 18 } 19 }, 20 error: function (data) { 21 alert("發送失敗。"); 22 } 23 });
CheckPremissionFilter:

1 public class CheckPermissionFilterAttribute : AuthorizationFilterAttribute 2 { 3 #region 驗證權限 4 /// <summary> 5 /// 驗證權限 6 /// </summary> 7 public override void OnAuthorization(HttpActionContext actionContext) 8 { 9 var token = GetHttpToken(actionContext.Request.Headers); 10 if (SMSTokenHelper.CheckToken(token)) 11 { 12 base.OnAuthorization(actionContext); 13 } 14 else 15 { 16 throw new BizException("當前請求沒有訪問權限!"); 17 } 18 } 19 #endregion 20 21 #region 輔助方法 22 /// <summary> 23 /// 獲得請求頭中的token信息 24 /// </summary> 25 private string GetHttpToken(HttpRequestHeaders headers) 26 { 27 IEnumerable<string> tokenCollection; 28 if (headers.TryGetValues(ConfigHelper.SMSCENTER_TOKEN_NAME, out tokenCollection)) 29 { 30 var token = tokenCollection.FirstOrDefault(); 31 return token; 32 } 33 return null; 34 } 35 #endregion 36 }
SMSTokenHelper:

1 internal class SMSTokenHelper 2 { 3 public static string CreateToken(string eid) 4 { 5 //1.使用eid,moduleID,當前時間構建認證對象 6 var token = new SMSToken() 7 { 8 EID = eid, 9 ModuleID = ConfigHelper.SMSCENTER_MODULE_ID, 10 CurrentTime = DateTime.Now.ToString() 11 }; 12 13 //2.轉化為Json字符串 14 var tokenString = JsonConvert.SerializeObject(token); 15 //3.將json字符串加密 16 var encryptToken = DESHelper.DESEncrypt(tokenString); 17 return encryptToken; 18 } 19 20 public static bool CheckToken(string token) 21 { 22 try 23 { 24 //1.解密 25 var tokenString = DESHelper.DESDecrypt(token); 26 //2.反序列化為對象 27 var smstoken = JsonConvert.DeserializeObject<SMSToken>(tokenString); 28 //3.驗證結果 29 if (ConfigHelper.SMSCENTER_MODULE_ID == smstoken.ModuleID) 30 { 31 return true; 32 } 33 } 34 catch { } 35 return false; 36 } 37 38 private class SMSToken 39 { 40 public string EID { get; set; } 41 public string ModuleID { get; set; } 42 public string CurrentTime { get; set; } 43 } 44 45 #region 輔助類 46 /// <summary> 47 /// DES加密解密 48 /// </summary> 49 private class DESHelper 50 { 51 /// <summary> 52 /// 獲取密鑰 53 /// </summary> 54 private static string Key 55 { 56 get { return @"P@+#wG+Z"; } 57 } 58 59 /// <summary> 60 /// 獲取向量 61 /// </summary> 62 private static string IV 63 { 64 get { return @"L%n67}G/Mk@k%:~Y"; } 65 } 66 67 /// <summary> 68 /// DES加密 69 /// </summary> 70 /// <param name="plainStr">明文字符串</param> 71 /// <returns>密文</returns> 72 public static string DESEncrypt(string plainStr) 73 { 74 byte[] bKey = Encoding.UTF8.GetBytes(Key); 75 byte[] bIV = Encoding.UTF8.GetBytes(IV); 76 byte[] byteArray = Encoding.UTF8.GetBytes(plainStr); 77 78 string encrypt = null; 79 DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 80 try 81 { 82 using (MemoryStream mStream = new MemoryStream()) 83 { 84 using (CryptoStream cStream = new CryptoStream(mStream, des.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write)) 85 { 86 cStream.Write(byteArray, 0, byteArray.Length); 87 cStream.FlushFinalBlock(); 88 encrypt = Convert.ToBase64String(mStream.ToArray()); 89 } 90 } 91 } 92 catch { } 93 des.Clear(); 94 return encrypt; 95 } 96 97 /// <summary> 98 /// DES解密 99 /// </summary> 100 /// <param name="encryptStr">密文字符串</param> 101 /// <returns>明文</returns> 102 public static string DESDecrypt(string encryptStr) 103 { 104 byte[] bKey = Encoding.UTF8.GetBytes(Key); 105 byte[] bIV = Encoding.UTF8.GetBytes(IV); 106 byte[] byteArray = Convert.FromBase64String(encryptStr); 107 108 string decrypt = null; 109 DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 110 try 111 { 112 using (MemoryStream mStream = new MemoryStream()) 113 { 114 using (CryptoStream cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write)) 115 { 116 cStream.Write(byteArray, 0, byteArray.Length); 117 cStream.FlushFinalBlock(); 118 decrypt = Encoding.UTF8.GetString(mStream.ToArray()); 119 } 120 } 121 } 122 catch { } 123 des.Clear(); 124 return decrypt; 125 } 126 } 127 #endregion 128 } 129 }
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 [AttributeUsage(AttributeTargets.Parameter)] 2 public class FromBodyAttribute : CustomModelBinderAttribute 3 { 4 private static readonly ILog _logger = LogManager.GetLogger(typeof(FromBodyAttribute)); 5 6 public override IModelBinder GetBinder() 7 { 8 return new JsonModelBinder(); 9 } 10 11 public class JsonModelBinder : IModelBinder 12 { 13 public object BindModel(ControllerContext controllerContext, 14 ModelBindingContext bindingContext) 15 { 16 if (!controllerContext.HttpContext.Request.ContentType.ToLower().Contains("json")) 17 { 18 return null; 19 } 20 try 21 { 22 var jsonString = GetJsonString(controllerContext.HttpContext.Request.InputStream); 23 var result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType); 24 return result; 25 } 26 catch(Exception ex) 27 { 28 _logger.Warn(ex.Message, ex); 29 } 30 return null; 31 } 32 33 private string GetJsonString(Stream stream) 34 { 35 var jsonString = string.Empty; 36 using (var sr = new StreamReader(stream)) 37 { 38 stream.Position = 0; 39 jsonString = sr.ReadToEnd(); 40 } 41 return jsonString; 42 } 43 } 44 }
此外,WebAPI學習系列目錄如下,歡迎您的閱讀!
快速入門系列--WebAPI--04在老版本MVC4下的調整
參考資料:
-
(美)加洛韋. ASP.NET MVC 4高級編程(第4版)[M]. 北京:清華大學出版社, 2012.