近日逛招聘軟體,看到部分企業都要求會編寫、請求restFul的webapi。正巧這段時間較為清閑,於是乎打開vs準備開擼。 1.何為restFul? restFul是符合rest架構風格的網路API介面。 rest是一種軟體架構的編碼風格,是根據網路應用而去設計和開發的一種可以降低開發複雜度的編碼方 ...
近日逛招聘軟體,看到部分企業都要求會編寫、請求restFul的webapi。正巧這段時間較為清閑,於是乎打開vs準備開擼。
1.何為restFul?
restFul是符合rest架構風格的網路API介面。
rest是一種軟體架構的編碼風格,是根據網路應用而去設計和開發的一種可以降低開發複雜度的編碼方式,並且可以提高程式的可伸縮性(增減問題)。
幾種較為常見的操作類型:get(查詢)、post(新增)、put(修改)、delete(刪除)
2.restFul標準的WebApi搭建以及部署在iis上
在這裡為了方便,使用的ef框架,在創建讀/寫控制器時直接引用了模型類
控制器直接幫我幫crud的方法都寫好了,按照註釋的請求規則,可直接使用。代碼如下:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using System.Web.Http.Description; using webapi; namespace webapi.Controllers { public class ProductsController : ApiController { private DtoolsEntities db = new DtoolsEntities(); // GET: api/Products [HttpGet] public IQueryable<Product> GetProduct() { return db.Product.OrderByDescending(p => p.AddDate).Take(10); } // GET: api/Products/5 [HttpGet] [ResponseType(typeof(Product))] public IHttpActionResult GetProduct(int id) { Product product = db.Product.Find(id); if (product == null) { return NotFound(); } return Ok(product); } // PUT: api/Products/5 update [HttpPut] [ResponseType(typeof(void))] public IHttpActionResult PutProduct(int id, Product product) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (id != product.Id) { return BadRequest(); } product.AddDate = DateTime.Now; db.Entry(product).State = EntityState.Modified; try { db.SaveChanges(); } catch (DbUpdateConcurrencyException) { if (!ProductExists(id)) { return NotFound(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } // POST: api/Products [HttpPost] [ResponseType(typeof(Product))] public IHttpActionResult PostProduct(Product product) { if (!ModelState.IsValid) { return BadRequest(ModelState); } product.AddDate = DateTime.Now; db.Product.Add(product); db.SaveChanges(); return CreatedAtRoute("DefaultApi", new { id = product.Id }, product); } // DELETE: api/Products/5 [HttpDelete] [ResponseType(typeof(Product))] public IHttpActionResult DeleteProduct(int id) { Product product = db.Product.Find(id); if (product == null) { return NotFound(); } db.Product.Remove(product); db.SaveChanges(); return Ok(product); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } private bool ProductExists(int id) { return db.Product.Count(e => e.Id == id) > 0; } } }
每個控制器前根據類型最好指定[HttpGet] [HttpPost] [HttpPut] [HttpDelete],因為伺服器是根據請求類型自動映射匹配控制器名稱,加上特性,避免出錯。
weiapi設置中指定json格式,避免數據類型異常
webapi的搭建基本沒有問題了。接下來就是部署在iis上,這裡不多做描述,不懂如何部署iis可點擊這裡 (會被揍嗎?)
3.前臺ajax請求頁面的編寫
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <script src="js/jquery-3.3.1.js"></script> <script type="text/javascript"> //部署在iis上的webapi介面具體請求路徑 var httpUrl = "http://192.168.0.142:8018/api/Products"; $(function () { $.ajax({ url: httpUrl, type: "GET", dataType: "json", success: function (data) { $.each(data, function (index, item) { var tr = $("<tr/>"); $("<td/>").html(item["ProductName"]).appendTo(tr); $("<td/>").html(item["Brand"]).appendTo(tr); $("<td/>").html(item["ASIN"]).appendTo(tr); $("<td/>").html(item["SKU"]).appendTo(tr); $("<button id ='d' onclick='del(" + item["Id"] + ")'>").html("刪除").appendTo(tr); $("<button id ='u' onclick='update(" + item["Id"] + ")'>").html("更新").appendTo(tr); tr.appendTo("#tab"); }); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest + "," + textStatus + "," + errorThrown); } }); }); //修改 function update(id) { $.ajax({ url: httpUrl + "?id=" + id, type: "Put", data: { "id": id, "ProductName": '男士領帶', "Brand": '海瀾之家', "ASIN": 'SAD498AE1', "SKU": '98DA7E9QE-SDAE', "StoreName": '海瀾之家京東自營店' }, dataType: "json", success: function (data) { location.reload(); } }) } //新增 function add() { $.ajax({ url: httpUrl, type: "Post", data: { "ProductGuid": newGuid(), "ProductName": '男士襯衫', "Brand": '海瀾之家', "ASIN": 'SAD498AE1', "SKU": '98DA7E9QE-SDAE', "StoreName": '海瀾之家天貓旗艦店' }, dataType: "json", success: function (data) { location.reload(); } }) } //刪除 function del(id) { $.ajax({ url: httpUrl+"/" + id, type: "Delete", dataType: "json", success: function (data) { location.reload(); } }); } //生成guid function newGuid() { var guid = ""; for (var i = 1; i <= 32; i++) { var n = Math.floor(Math.random() * 16.0).toString(16); guid += n; if ((i == 8) || (i == 12) || (i == 16) || (i == 20)) guid += "-"; } return guid; } </script> </head> <body> <div> <table id="tab"> <tr> <th>產品名稱</th> <th>產品品牌</th> <th>ASIN</th> <th>SKU</th> </tr> </table> <button id="add" onclick="add()">add</button> </div> </body> </html>
前端,後端代碼都寫完了。只剩測試了。想都不用想肯定會出問題的,因為涉及到了跨域請求等,接下來就為大家解決問題。
問題一:ajax請求,涉及到cors跨域,請求失敗
問題介紹及原因分析:
CORS是一個W3C標準,全稱是”跨域資源共用”。它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,基本上目前所有的瀏覽器都實現了CORS標準,其實目前幾乎所有的瀏覽器ajax請求都是基於CORS機制的。
跨域問題一般發生在Javascript發起AJAX調用,因為瀏覽器對於這種請求,所給予的許可權是較低的,通常只允許調用本域中的資源,除非目標伺服器明確地告知它允許跨域調用。所以,跨域的問題雖然是由瀏覽器的行為產生出來的,但解決的方法卻 是在服務端。因為不可能要求所有客戶端降低安全性。
解決方案:
web.config修改配置文件,使服務端支持跨域請求
<system.webServer> <httpProtocol> <customHeaders> <!--伺服器端返回Response header 後接URL或* 表示允許--> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" />
<!--支持請求的類型--> <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" /> </customHeaders> </httpProtocol> <handlers> <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> </system.webServer>
問題二:get、post 可正常請求,put、delete 出現405(Method Not Allowed) 註意:我提交Put的請求,瀏覽器響應的是Request Method:PUT
問題介紹及原因分析:
有些人會問:我剛剛在服務端設置了允許put delete請求了,瀏覽器上服務端的響應也看到了支持put delete,為啥服務端還是拒絕呢?
一切都是iis的WebDAV(Web Distribution Authorization Versioning) Publish惹的禍,WebDAV是基於HTTP協議的拓展,添加了很多Method用於管理伺服器上的文件。若安裝了WebDAV,那麼iis所有的site都會預設使用WebDAV Module與WebDAV Handler。
WebDAV Handler的預設配置是處理如下 Method:PROPFIND,PROPPATCH,MKCOL,PUT,COPY,DELETE,MOVE,LOCK,UNLOCK。所以瀏覽器發送的put delete請求都會被攔截並交給WebDAV Handler來處理,並且WebDAV Handler會預設拒絕請求
解決方案:
既然我們找到了問題所在點,那麼解決方案應然而生,那就是在配置文件里移除ebDAVModule和WebDAVHandler或者在系統中直接移除WebDAV
<system.webServer> <!--以下配置為了讓IIS 7+支持Put/Delete方法--> <httpProtocol> <customHeaders> <!--伺服器端返回Response header 後接URL或* 表示允許--> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" /> </customHeaders> </httpProtocol> <validation validateIntegratedModeConfiguration="false"/> <handlers> <!--移除WebDAV協議--> <remove name="WebDAV"/> <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> <!--支持所有方式的請求--> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> <!--IIS7/7.5上必須加這個配置,否則訪問報錯--> <modules runAllManagedModulesForAllRequests="true"> <!--移除WebDAV協議--> <remove name="WebDAVModule"/> <remove name="TelemetryCorrelationHttpModule" /> <add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="integratedMode,managedHandler" /> </modules> </system.webServer>
問題三:put delete請求,又出現了405(Method Not Allowed)
問題介紹及原因分析:
大家發現沒有,問題二put提交,Request Method就是put,Delete提交,Request Method就是Delete。然而在這裡統統都是OPTIONS。那麼這個OPTIONS到底是個啥呢?
Preflighted Requests(預檢請求)
Preflighted Requests是CORS中一種透明伺服器驗證機制。預檢請求首先需要向另外一個功能變數名稱的資源發送一個 HTTP OPTIONS 請求頭,其目的就是為了判斷實際發送的請求是否是安全的。
下麵的2種情況需要進行預檢:
1、簡單請求,比如使用Content-Type 為 application/xml 或 text/xml 的 POST 請求;
2、請求中設置自定義頭,比如 X-JSON、X-MENGXIANHUI 等。
原來put、delete請求如果頭部設置了XMLHttpRequest.setRequestHeader("Content-Type", "application/json")之前還發送了一次預檢請求。
解決方案:
既然是多的一次請求,那我們就在服務端過濾掉就好了。
Global.asac添加以下方法就行了
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace webapi { public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } protected void Application_BeginRequest() { if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS") { Response.End(); } } } }
到這裡,ajax跨域實現RestFul請求,已經是能正常運行了。剩下的只是安全校驗、身份認證、異常記錄等,就能放到生產環境了。這裡就不多做描述了,畢竟博主還是上班族...
如有錯誤,歡迎大家指正~