在新出的MVC4中,增加了WebAPI,用於提供REST風格的WebService,新生成的WebAPI項目和典型的MVC項目一樣,包含主要的Models、Views、Controllers等文件夾和Global.asax文件。Views對於WebAPI來說沒有太大的用途,Models中的Model主要用於保存Service和Client交互的對象,這些對象默認情況下會被轉換為Json格式的數據迚行傳輸,Controllers中的Controller對應於WebService來說是一個Resource,用於提供服務。和普通的MVC一樣,Global.asax用於配置路由規則。
環境准備
建議使用VS2012以上版本創建WebAPI,如果是使用VS2010,需要安裝VS2010 SP1升級包,MVC4升級包,打開VS2012創建如下:
第一步:新建ASP.NET Web應用程序
第二步:建議WebAPI
新生成的WebAPI項目和典型的MVC項目一樣,包含主要的Models,Views,Controllers等文件夾和Global.asax文件
注意:再次強調Views對於WebAPI來說沒有太大的用途,Models中的Model主要用於保存Service和Client交互的對象,這些對象默認情況下會被轉換為Json格式的數據進行傳輸,Controllers中的Controller對應於WebService來說是一個Resource,用於提供服務。和普通的MVC一樣,Global.asax用於配置路由規則
(二)Models
和WCF中的數據契約形成鮮明對比的是,MVC WebAPI中的Model就是簡單的POCO,沒有任何別的東西,如,你可以創建如下的Model
public class UserModel { public int Id { get; set; } public string UserName { get; set; } public string PassWord { get; set; } }
注意:Model必須提供public的屬性,用於json或xml反序列化時的賦值
(三)Controllers
MVC WebAPI中的Controllers和普通MVC的Controllers類似,不過不再繼承於Controller,而改為繼承API的ApiController,一個Controller可以包含多個Action,這些Action響應請求的方法與Global中配置的路由規則有關,在后面結束Global時統一說明
(四)Global
默認情況下,模板自帶了兩個路由規則,分別對應於WebAPI和普通MVC的Web請求,默認的WebAPI路由規則如下
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
可以看到,默認路由使用的固定的api作為Uri的先導,按照微軟官方的說法,用於區分普通Web請求和WebService的請求路徑:
可以看到,默認的路由規則只指向了Controller,沒有指向具體的Action,因為默認情況下,對於Controller中的Action的匹配是和Action的方法名相關聯的:具體來說,如果使用上面的路由規則,對應下面的Controller:
public class UserController : ApiController { public List<UserModel> allModeList = new List<UserModel>() { new UserModel(){ Id=1,UserName="zhang", PassWord="123"}, new UserModel(){ Id=2,UserName="lishi", PassWord="123456"}, new UserModel(){ Id=3,UserName="wang", PassWord="1234567"} }; //Get api/User/ public IEnumerable<UserModel> GetAll() { return allModeList; } //Get api/User/1 public IEnumerable<UserModel> GetOne(int id) { return allModeList.FindAll((m) => { return m.Id == id; }); } //POST api/User/ public bool PostNew(UserModel user) { try { allModeList.Add(user); return true; } catch { return false; } } //Delete api/User/ public int DeleteAll() { return allModeList.RemoveAll((mode) => { return true; }); } //Delete api/User/1 public int DeleteOne(int id) { return allModeList.RemoveAll((m) => { return m.Id == id; }); } //Put api/User public int PutOne(int id, UserModel user) { List<UserModel> upDataList = allModeList.FindAll((mode) => { return mode.Id == id; }); foreach (var mode in upDataList) { mode.PassWord = user.PassWord; mode.UserName = user.UserName; } return upDataList.Count; } }
則,會有下面的對應關系:
URL | HttpMethod | 對應的Action名 |
---|---|---|
/api/User | GET | GetALL |
/api/User/1 | GET | GetOne |
/api/User | POST | PostNew |
/api/User/1 | DELETE | DeleteOne |
/api/User | DELETE | DeleteALL |
/api/User | PUT | PutOne |
客戶端JS調用
function getAll() { $.ajax({ url: "api/User/", type: 'GET', success: function (data) { document.getElementById("modes").innerHTML = ""; $.each(data, function (key, val) { var str = val.UserName + ': ' + val.PassWord; $('<li/>', { html: str }).appendTo($('#modes')); }); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); } function find() { $.ajax({ url: "api/User/1" , type: 'GET', success: function (data) { document.getElementById("modes").innerHTML = ""; $.each(data, function (key, val) { var str = val.UserName + ': ' + val.PassWord; $('<li/>', { html: str }).appendTo($('#modes')); }); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); } function add() { $.ajax({ url: "api/User/", type: "POST", dataType: "json", data: { "Id":4,"UserName": "admin", "PassWord": "666666"}, success: function (data) { getAll(); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); } function removeUser() { $.ajax({ url: "api/User/3", type: 'DELETE', success: function (data) { document.getElementById("modes").innerHTML = ""; getAll(); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); } function removeAll() { $.ajax({ url: "api/User/", type: 'DELETE', success: function (data) { document.getElementById("modes").innerHTML = ""; getAll(); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); } function udpate() { $.ajax({ url: "api/User/1", type: 'PUT', dataType: "json", data: { Id: 1, "UserName": "admin", "PassWord": "666666" }, success: function (data) { document.getElementById("modes").innerHTML = ""; getAll(); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); }
這樣就實現了最基本的CRUD操作。
擴展需求
問題1:我想按照用戶名稱(UserName)進行查詢,怎么辦?
辦法:第一步:在UserController類中加一個方法名稱叫:GetUserByName,如下所示:
public UserModel GetUserByName(string userName) { return allModeList.Find((m) => { return m.UserName.Equals(userName); }); }
第二步:在客戶端index.cshtml中調用
function getUserByName() { $.ajax({ url: "api/User/zhang", type: 'GET', success: function (data) { document.getElementById("modes").innerHTML = ""; var str = data.UserName + ': ' + data.PassWord; $('<li/>', { html: str }).appendTo($('#modes')); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); }
如果URL是: url: "api/User/zhang",將會報錯:Bad Request
原因是他會自動調用我們的GetOne(int id) 這個方法,類型轉換出錯
解決辦法:
改變URL為: url: "api/User/?userName=zhang",
問題2:我想按用戶名稱(UserName) 和用戶密碼(PassWord)一起來進行查詢,怎么辦?
解決辦法
第一步:UserController類中,可以重載一個GetUserByName的方法,如下所示:
public UserModel GetUserByName(string userName) { return allModeList.Find((m) => { return m.UserName.Equals(userName); }); }
第二步:客戶端調用:
function getUserByName() { $.ajax({ url: "api/User/?userName=zhang&passWord=123", //這里尤其需要注意 type: 'GET', success: function (data) { document.getElementById("modes").innerHTML = ""; var str = data.UserName + ': ' + data.PassWord; $('<li/>', { html: str }).appendTo($('#modes')); } }).fail( function (xhr, textStatus, err) { alert('Error: ' + err); }); }
路由規則擴展
和普通的MVC一樣,MVC WebAPI支持自定義的路由規則,如:在上面的操作中,路由規則使用
"api/{controller}/{id}"
則限定了使用GET方式利用URL來傳值時,controller后面的接收參數名為id,但是在Controller中,如果GetOne方法的接收參數名為key,是不會被匹配的,這是只需要新增一個新的路由規則,或修改原先的路由規則為:
"api/{controller}/{key}",如下所示:
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{key}", defaults: new { key = RouteParameter.Optional } );
當然,可以對路由進行更深的擴展,如:擴展成和普通MVC一樣的路由:
"api/{controller}/{action}/{id}"
這樣,就要求同時使用Action和HTTP方法進行匹配當然,根據微軟的說法,這種使用是不被推薦的,因為這不符合大家對WebService的一般認知:
使用Attribute聲明HTTP方法
[HttpGet] public IEnumerable<TestUseMode> FindAll() [HttpGet] public IEnumerable<TestUseMode> FindByKey(string key) [HttpPost] public bool Add(TestUseMode mode) [HttpDelete] public int RemoveByKey(string key) [HttpDelete] public int RemoveAll() [HttpPut] public int UpdateByKey(string key, string value) [NonAction] public string GetPrivateData()
當然,我只列出了方法名,而不是這些方法真的沒有方法體...方法體是不變的,NoAction表示這個方法是不接收請求的,即使以GET開頭。如果感覺常規的GET,POST,DELETE,PUT不夠用,還可以使用AcceptVerbs的方式來聲明HTTP方法,如:
[AcceptVerbs("MKCOL", "HEAD")] public int UpdateByKey(string key, string value) { List<TestUseMode> upDataList = allModeList.FindAll((mode) => { if (mode.ModeKey == key) return true; return false; }); foreach(var mode in upDataList) { mode.ModeValue = value; } return upDataList.Count; }
附:什么是REST風格? 參考:什么是REST風格
http://hi.baidu.com/yankaiwei/item/1f0b37dd922d53ef3cc2cb69
第二部分:綜合示例:應用ASP.NET MVC4+WebAPI+FluentData開發Web應用
第一步:創建數據庫
NorthWind數據庫的Customers表
Create DataBase NorthWind Go Use NorthWind Go CREATE TABLE [dbo].[Customers]( [CustomerID] [nchar](5) NOT NULL, [CompanyName] [nvarchar](40) NOT NULL, [ContactName] [nvarchar](30) NULL, [ContactTitle] [nvarchar](30) NULL, [Address] [nvarchar](60) NULL, [City] [nvarchar](15) NULL, [Region] [nvarchar](15) NULL, [PostalCode] [nvarchar](10) NULL, [Country] [nvarchar](15) NULL, [Phone] [nvarchar](24) NULL, [Fax] [nvarchar](24) NULL, CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ( [CustomerID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
第二步:創建 FluentData.Entity層,創建Customer實體類
namespace FluentData.Entity { public class Customer { public string CustomerID { get; set; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Fax { get; set; } } }
第三步:利用FluentData做數據的持久化
首先引入FluentData.cs (見附件)
其次:創建DBHelper類,代碼如下:
public class DBHelper { public static IDbContext Context() { //return new DbContext().ConnectionString("server=127.0.0.1;uid=sa;pwd=sa;database=TestDB", new SqlServerProvider()); return new DbContext().ConnectionStringName("connString", new SqlServerProvider()); } }
然后不要忘記修改ASP.NET MVC層所在的Web.config,加入數據庫連結字符串:
<connectionStrings> <add name="connString" connectionString="server=127.0.0.1;database=Northwind;uid=sa;pwd=sa;"/> </connectionStrings>
第三步:創建 CustomerService數據持久化類,代碼如下:
public class CustomerService { private IDbContext context = DBHelper.Context(); public Customer Select(string customerId){ return context.Select<Customer>("*").From("Customers").Where("CustomerID=@0").Parameters(customerId) .QuerySingle(); } public List<Customer> SelectAll() { return context.Select<Customer>("*").From("Customers").QueryMany(); } public List<Customer> SelectAll(string sortExpression) { if (String.IsNullOrEmpty(sortExpression)) return null; return context.Select<Customer>("*").From("Customers").OrderBy(sortExpression).QueryMany(); } public List<Customer> SelectAll(int currentPageIndex,int maxRows, string sortExpression) { var select = context.Select<Customer>("*").From("Customers"); if (maxRows > 0) { if (currentPageIndex == 0) currentPageIndex = 1; select.Paging(currentPageIndex, maxRows); } if (!string.IsNullOrEmpty(sortExpression)) { select.OrderBy(sortExpression); } return select.QueryMany(); } public int CountAll() { return context.Sql("select count(*) from Customers").QuerySingle<int>(); } public int Insert(Customer customer) { return context.Insert<Customer>("Customers", customer).Execute(); } public int Update(Customer customer) { return context.Update<Customer>("Customers", customer).Where("CustomerID", customer.CustomerID).Execute(); } public int Delete(string customerId) { return context.Delete("Customers").Where("CustomerID", customerId).Execute(); } public int Delete(Customer customer) { return this.Delete(customer.CustomerID); } }
第四步:Web API,創建CustomerController
注意要引用:FluentData.Entity及FluentData.DAL 程序集
public class CustomerController : ApiController { private CustomerService customerService = new CustomerService(); //Select All public IEnumerable<Customer> Get() { return customerService.SelectAll(); } //Select By Id public Customer Get(string id) { return customerService.Select(id); } //Insert public void Post(Customer customer) { customerService.Insert(customer); } //Update public void Put(string id, Customer obj) { customerService.Update(obj); } //Delete public void Delete(string id) { customerService.Delete(id); } }
第五步:View層代碼
namespace MyWebApI.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult Test() { return View(); } public ActionResult CustomerManager() { return View(); } } }
然后創建View
<table id="customerTable" border="1" cellpadding="3" style="width:700px"> <tr> <th>Customer ID</th> <th>Company Name</th> <th>Contact Name</th> <th>Country</th> <th>Actions</th> </tr> <tr> <td><input type="text" id="txtCustomerId" style="width:100px" size="5"/></td> <td><input type="text" id="txtCompanyName" style="width:150px" /></td> <td><input type="text" id="txtContactName" style="width:150px"/></td> <td><input type="text" id="txtCountry" style="width:150px"/></td> <td><input type="button" name="btnInsert" value="Insert" style="width:150px"/></td> </tr> </table> <script type="text/javascript"> $(function () { $.getJSON("api/Customer", LoadCustomers); }); function LoadCustomers(data) { $("#customerTable").find("tr:gt(1)").remove(); $.each(data, function (key, val) { var tableRow = '<tr>' + '<td>' + val.CustomerID + '</td>' + '<td><input type="text" value="' + val.CompanyName + '" /></td>' + '<td><input type="text" value="' + val.ContactName + '" /></td>' + '<td><input type="text" value="' + val.Country + '" /></td>' + '<td><input type="button" name="btnUpdate" value="修改" /> <input type="button" name="btnDelete" value="刪除" /></td>' + '</tr>'; $('#customerTable').append(tableRow); }); $("input[name='btnInsert']").click(OnInsert); $("input[name='btnUpdate']").click(OnUpdate); $("input[name='btnDelete']").click(OnDelete); } function OnInsert(evt) { var customerId = $("#txtCustomerId").val(); var companyName = $("#txtCompanyName").val(); var contactName = $("#txtContactName").val(); var country = $("#txtCountry").val(); var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}'; $.ajax({ type: 'POST', url: '/api/Customer/', data: data, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { $("#txtCustomerId").val(''); $("#txtCompanyName").val(''); $("#txtContactName").val(''); $("#txtCountry").val(''); $.getJSON("api/customers" + new Date().getTime(), LoadCustomers); alert('添加成功!'); } }).fail( function (xhr, textStatus, err) { alert('添加失敗,原因如下: ' + err); }); } function OnUpdate(evt) { var input; var customerId = $(this).parent().parent().children().get(0).innerHTML; input = $($(this).parent().parent().children().get(1)).find("input"); //input.removeAttr("disabled"); var companyName = input.val(); input = $($(this).parent().parent().children().get(2)).find("input"); //input.removeAttr("disabled"); var contactName = input.val(); input = $($(this).parent().parent().children().get(3)).find("input"); //input.removeAttr("disabled"); var country = input.val(); var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}'; $.ajax({ type: 'PUT', url: '/api/Customer/' + customerId, data: data, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { $.getJSON("api/Customer" + new Date().getTime(), LoadCustomers); alert('修改成功 !'); } }).fail( function (xhr, textStatus, err) { alert('修改失敗,原因如下: ' + err); }); } function OnDelete(evt) { var customerId = $(this).parent().parent().children().get(0).innerHTML; //var data = '{"id":"' + customerId + '"}'; //var row = $(this).parent().parent(); $.ajax({ type: 'DELETE', url: '/api/Customer/' + customerId, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { $.getJSON("api/Customer?"+new Date().getTime(), LoadCustomers); alert('成功刪除!'); } }).fail( function (xhr, textStatus, err) { alert('刪除失敗,原因如下: ' + err); }); } </script>
第三部分:Web API高級部分
在第一部分和大家一起學習了建立基本的WebAPI應用,第二部分寫了一個綜合示例,立刻就有人想到了一些問題:1.客戶端和WebService/WebAPI之間文件傳輸2.客戶端或者服務端的安全控制要解決這些問題,要了解一下WebAPI的基本工作方式。
(一)WebAPI中工作的Class
在MVC中大家都知道,獲取Request和Response使用HttpRequest和HttpResponse兩個類,在WebAPI中使用兩外兩個類:HttpRequestMessage 和HttpResponseMessage,分別用於封裝Requset和Response。除了這兩個類之外,還有一個常見的抽象 類:HttpMessageHandler,用於過濾和加工HttpRequestMessage和HttpResponseMessage
(二)解決第一個問題:客戶端和WebService之間文件傳輸其 實第一個問題之所以被提出來應該是和客戶端有關,如果客戶端的請求是我們手寫提交的,比如使用HttpClient封裝的請求,則要傳遞文件之前,我們一 般會進行一次序列化,轉化為二進制數組之類的,在網絡上傳輸。這樣的話,在Controller中的Action參數里,我們只需要接收這個二進制數組類 型的對象就可以了。但是如果客戶端是Web Form呢,比如我們提交一個Form到指定的Controller的Action中,這個Action要接收什么類型的參數呢?或者我們問另外一個問題,如果我將Web Form提交到一個WebAPI的Action中 ,我要怎么去取出這個表單中的數據呢?其 實我們應該想到:我們的Action設置的參數之所以能夠被賦值,是因為WebAPI的架構中在調用Action時將HTTP請求中的數據解析出來分別賦 值給Action中的參數,如果真是這樣的話,我們只需要在Action中獲取到HTTP請求,然后直接獲取請求里面的數據,就能解決上面的問題。這 種想法是正確的,只不過,此時的HTTP請求已經不是最原始的HTTP Request,而是已經被轉化成了HttpRequestMessage,在Action中,我們可以直接調用base.Requet來得到這個 HttpRequestMessage實例,通過這個實例我們就可以隨心所欲的取出HTTP請求中想要的數據
2.1從RequestMessage中獲取普通表單數據
這里的普通表單是指不包含File的表單,也就是說表單的enctype值不是multipart/form-data,這時,表單的數據默認情況下是以Json來傳遞的如下頁面
<form name="form" action="~/api/File/Register" method="post"> <input type="text" name="userName" /> <br /> <input type="text" name="passWord" /> <br /> <input type="submit" value="Submit" /> </form>
ApiContoller:
public class FileController : ApiController { [HttpPost] public async Task Register() { HttpContent content = Request.Content; var jsonValue = await content.ReadAsStringAsync(); Console.WriteLine(jsonValue); } }
執行:userName字段輸入admin,passWord字段輸入123456
傳遞到服務器端輸出的是:userName=admin&passWord=12345
2.2從RequestMessage中獲取multipart表單數據將view頁面改寫為:
<form name="form" action="~/api/File/SubmitFile" method="post" enctype="multipart/form-data" > <input type="text" name="userName2" /> <br /> <input type="text" name="passWord2" /> <br /> <input type="file" name="file" id="upFile" /> <br /> <input type="submit" value="Submit" /> </form>
ApiController代碼:
public async Task<string> SubmitFile() { // 檢查是否是 multipart/form-data if (!Request.Content.IsMimeMultipartContent("form-data")) throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); var path = HttpContext.Current.Server.MapPath("~/File"); // 設置上傳目錄 var provider = new MultipartFormDataStreamProvider(path); // 接收數據,並保存文件 var bodyparts = await Request.Content.ReadAsMultipartAsync(provider); var file = provider.FileData[0];//provider.FormData string orfilename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"'); FileInfo fileinfo = new FileInfo(file.LocalFileName); String ymd = DateTime.Now.ToString("yyyyMMdd", System.Globalization.DateTimeFormatInfo.InvariantInfo); String newFileName = DateTime.Now.ToString("yyyyMMddHHmmss_ffff", System.Globalization.DateTimeFormatInfo.InvariantInfo); string fileExt = orfilename.Substring(orfilename.LastIndexOf('.')); fileinfo.CopyTo(Path.Combine(path, newFileName + fileExt), true); fileinfo.Delete(); string result = ""; // 獲取表單數據 result += "formData userName: " + bodyparts.FormData["userName1"]; result += "<br />"; // 獲取文件數據 result += "fileData headers: " + bodyparts.FileData[0].Headers; // 上傳文件相關的頭信息 result += "<br />"; result += "fileData localFileName: " + bodyparts.FileData[0].LocalFileName; // 文件在服務端的保存地址,需要的話自行 rename 或 move return result; }
還有一種簡單的方式
public string Post() { HttpPostedFile file = HttpContext.Current.Request.Files[0]; string strPath = "D:\\MyProjects\\StudySolution\\RestDemo\\Upload\\test2.rar" ; file.SaveAs(strPath); string result = "0"; return result; }
注:上述的文件上傳代碼涉及到async、Task、await 這些關鍵字是 asp.net mvc 中異步編程的知識點,在這里暫不做過多解釋,不了解的同學可以去先了解一下這塊的內容,后期在我的系列主題文章中也還會有這塊知識點的講解,敬請關注!
解決第二個問題:客戶端或者服務端的安全控制
WebAPI的工作方式:HTTP的請求最先是被傳遞到HOST中的,如果WebAPI是被寄宿在IIS上的,這個HOST就是IIS上,HOST是沒有能力也沒有必要進行請求的處理的,請求通過HOST被轉發給了HttPServer此時已經進入WebAPI的處理加工范圍,HttpServer是 System.Net.HTTP中的一個類,通過HttpServer,請求被封裝成了WebAPI中的請求承載 類:HttpRequestMessage,這個封裝后的請求可以經過一系列自定義的Handler來處理,這些handler串聯成一個 pipeline,最后請求會被傳遞給HttpControlDispather,這個類通過對路由表的檢索來確定請求將被轉發到的具體的 Controller中的Action。
由此我們早就可以看出,想要解決第二個問題,可以直接在Handler PipeLine中進行,這種AOP風格的過濾器(攔截器)在REST的Webservice的安全驗證中應用很廣,一般大家比較樂於在HTTP頭或者在 HTTP請求的URL中加上身份驗證字段進行身份驗證,下面舉一個在Http頭中添加身份驗證信息的小例子:
3.1客戶端客戶端的customhandler用於將身份驗證信息添加入報頭
class RequestCheckHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { request.Headers.Add("keyword", "ibeifeng"); return base.SendAsync(request, cancellationToken); } }
注:1.customhandler繼承自DelegatingHandler類,上面已經說過,WebAPI的客戶端和服務端被設計為相互對應的兩套結構,所以不論是在客戶端還是服務端,customhandler都是繼承自DelegatingHandler類2.DelegatingHandler的sendAsync方法便是處理請求和接受請求時會被調用的方法,該方法返回值是HttPResponseMessage,接收的值為HttpRequestMessage,符合我們的一般認知3.方法的最后,調用base.SendAsync是將Request繼續向該pipeline的其他customHandler傳遞,並獲取其返回值。由於該方法不包含Response的處理邏輯,只需直接將上一個CustomHandler的返回值直接返回
客戶端主程序:
static void Main(string[] args) { HttpClient client = new HttpClient(new RequestCheckHandler() { InnerHandler = new HttpClientHandler() }); HttpResponseMessage response = client.GetAsync("http://localhost:47673/api/File/GetUserInfo?userName=admin").Result; response.Content.ReadAsStringAsync().ContinueWith((str) => { Console.WriteLine(str.Result); }); }
客戶端的主程序創建了一個HttpClient,HttpClient可以接受一個參數,該參數就是CustomHandler,此處我們嵌入了我們定義的 RequestUpHandler,用於對Request報頭進行嵌入身份驗證碼的處理,CustomHandler通過InnerHandler屬性嵌 入其內置的下一個CustomHandler,此處,由於沒有下一個CustomerHandler,我們直接嵌入HttpClientHandler用 於將HttpRequestMessage轉化為HTTP 請求、將HTTP響應轉化為HttpResponseMessage
3.2服務端服務端的customHandler用於解析HTTP報頭中的身份認證碼
public class SecurityHandler : DelegatingHandler { protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { int matchHeaderCount = request.Headers.Count((item) => { if ("keyword".Equals(item.Key)) { foreach (var str in item.Value) { if ("ibeifeng".Equals(str)) { return true; } } } return false; }); if (matchHeaderCount>0) { return base.SendAsync(request, cancellationToken); } return Task.Factory.StartNew<HttpResponseMessage>(() => { return new HttpResponseMessage(HttpStatusCode.Forbidden); }); } }
另:
FileController中加一個測試方法:
[HttpGet] public string GetUserInfo(string userName) { if (userName == "admin") { return "success"; } else { return "failed"; } }
注:代碼的處理邏輯很簡單:如果身份驗證碼匹配成功,則通過base.SendAsync繼續將請求向下傳遞,否則返回直接中斷請求的傳遞,直接返回一個響應碼為403的響應,指示沒有權限。注意由於SendAsync的返回值需要封裝在Task之中,所以需要使用Task.Factory.StartNew將返回值包含在Task中將customHandler注入到HOST中本例中WebAPI HOST在IIS上,所以我們只需將我們定義的CustomHandler在Application_Start中定義即可
protected void Application_Start() { //省略其他邏輯代碼 GlobalConfiguration.Configuration.MessageHandlers.Add(new SecurityHandler ()); }
由於WebAPI Host在IIS上,所以HttpServer和HttpControllerDispatcher不用我們手工處理
在加上上面的處理后,如果沒有身份驗證碼的請求,會得到如下的響應
總結
1.使用WebAPI的目的
當你遇到以下這些情況的時候,就可以考慮使用Web API了。
a. 需要Web Service但是不需要SOAP
b. 需要在已有的WCF服務基礎上建立non-soap-based http服務
c. 只想發布一些簡單的Http服務,不想使用相對復雜的WCF配置
d. 發布的服務可能會被帶寬受限的設備訪問
e. 希望使用開源框架,關鍵時候可以自己調試或者自定義一下框架
2.使用WebAPI的幾種方式 與注意事項
3.使用WebAPI實現文件上傳
4.如何加強WebAPI的安全性