[WCF REST] 一個簡單的REST服務實例
微軟在WCF 3.5中就通過提供基於Web HTTP的編程模式使我們很容易地創建基於REST的服務,WCF 4.0中對此進行了較大的改進。為了讓讀者對REST在WCF中的應用有一個大致的了解,我們先來進行一個簡單的實例演示。 [源代碼從這裏下載]
在這個實例中,我們創建一個簡單的服務來管理員工的基本信息。至於實例程序的結構,我們依然采用熟悉的包含三個項目(Service.Interface、Service和Client)的解決方案。如下所示的是定義在Service.Interface中用於表示員工的Employee類的定義,它是一個數據契約。
1: [DataContract(Namespace="https://www.artech.com/")]
2: public class Employee
3: {
4: [DataMember]
5: public string Id { get; set; }
6:
7: [DataMember]
8: public string Name { get; set; }
9:
10: [DataMember]
11: public string Department { get; set; }
12:
13: [DataMember]
14: public string Grade { get; set; }
15:
16: public override string ToString()
17: {
18: return string.Format("ID: {0,-5}姓名: {1, -5}級別: {2, -4} 部門: {3}",Id, Name, Grade, Department);
19: }
20: }
接下來我們定義了如下一個表示服務契約的接口IEmployeesService。和基於SOAP的服務契約定義不同,我們無需在相應的操作方法上麵應用OperationContractAttribute特性,但是應用在接口/類上的ServiceContractAttribute特性仍是必需的。在這裏替換OperationContractAttribute特性的分別是WebGetAttribute和WebInvokeAttribute,它們均定義在System.ServiceModel.Web程序集中。
1: [ServiceContract(Namespace="https://www.artech.com/")]
2: public interface IEmployees
3: {
4: [WebGet(UriTemplate = "all")]
5: IEnumerable<Employee> GetAll();
6:
7: [WebGet(UriTemplate = "{id}")]
8: Employee Get(string id);
9:
10: [WebInvoke(UriTemplate = "/", Method = "POST")]
11: void Create(Employee employee);
12:
13: [WebInvoke(UriTemplate = "/", Method = "PUT")]
14: void Update(Employee employee);
15:
16: [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
17: void Delete(string id);
18: }
契約接口IEmployeesService中定義了5個操作,分別用於實現針對員工數據的獲取、添加、修改和刪除。按照REST設計原則,我們將被操作的員工信息體現為某種網絡資源,而操作類型最好與相應的HTTP方法相匹配。在操作方法中針對資源的操作類型與HTTP方法之間的匹配是通過應用在它們上麵的WebGetAttribute和WebInvokeAttribute特性來體現。
WebGetAttribute針對GET方法,而其他的HTTP方法則通過WebInvokeAttribute的Method屬性來體現。在IEmployeesService中,兩個用於獲取員工信息GetAll和Get方法均應用了WebGetAttribute特性,而其他的Create、Update和Delete方法在應用了WebInvokeAttribute特性,並且其Method屬性被分別設置為PUT、POST和DELETE。
WebGetAttribute和WebInvokeAttribute和均具有相同的屬性UriTemplate,該屬性用於定義作為最終操作URI的模板。我們不僅可以通過UriTemplate屬性為操作指定一個相對於終結點地址的靜態路徑,還可以通過占位符實現路徑中的動態部分與參數之間的映射。
同樣以定義在契約接口IEmployeesService中的5個操作方法為例,如果終結點地址為https://127.0.0.1:3721/employees,由於用於返回所有員工列表的GetAll操作的UriTemplate被設置“All”,所以其地址為https://127.0.0.1:3721/employees。用於返回指定員工ID的Get操作的UriTemplate被設置成“{id}”,意味著我們直接在表示請求地址的URI中指定員工的ID,而它會自動映射為該操作方法的參數id。用於刪除某個指定員工的Delete操作具有相同的UriTemplate設置,而用於創建添加新員工和修改現有員工信息的Create和Update操作,由於作為參數的Employee對象具有ID屬性,所以直接采用終結點地址。
在控製台程序Service中我們定義了如下一個實現了契約接口IEmployeesService的服務類型EmployeesService。簡單 起見,我們直接通過一個靜態字段employees表示存儲的員工列表,該靜態字段在初始化的工作中被添加了兩個ID分別為001和002的Employee對象。針對員工信息的獲取、添加、修改和刪除的操作均在此列表中進行。
1: public class EmployeesService : IEmployees
2: {
3: private static IList<Employee> employees = new List<Employee>
4: {
5: new Employee{ Id = "001", Name="張三", Department="開發部", Grade = "G7"},
6: new Employee{ Id = "002", Name="李四", Department="人事部", Grade = "G6"}
7: };
8: public Employee Get(string id)
9: {
10: Employee employee = employees.FirstOrDefault(e => e.Id == id);
11: if (null == employee)
12: {
13: WebOperationContext.Current.OutgoingResponse.StatusCode =
14: HttpStatusCode.NotFound;
15: }
16: return employee;
17: }
18:
19: public void Create(Employee employee)
20: {
21: employees.Add(employee);
22: }
23:
24: public void Update(Employee employee)
25: {
26: this.Delete(employee.Id);
27: employees.Add(employee);
28: }
29:
30: public void Delete(string id)
31: {
32: Employee employee = this.Get(id);
33: if (null != employee)
34: {
35: employees.Remove(employee);
36: }
37: }
38:
39: public IEnumerable<Employee> GetAll()
40: {
41: return employees;
42: }
43: }
值得一提的是,不論是用於獲取某個指定ID的員工信息的Get方法,還是用於修改和刪除員工記錄的Update和Delete方法,當指定ID的員工不存在時都通過WebOperationContext表示當前Web操作上下文的對象將回複狀態設置為NotFound(即404 Not Found),這體現了我們的服務是基於Web的。
接下來我們通過自我寄宿的方式對上麵定義的EmployeesService服務進行寄宿,下麵是相應的配置。我們為寄宿的服務添加了唯一一個終結點,並簡單地指定了其ABC三要素。和我們之前配置的終結點不同的是,在這裏我們采用的綁定類型為WebHttpBinding。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Artech.WcfServices.Service.EmployeesService">
5: <endpoint address="https://127.0.0.1:3721/employees"
6: binding="webHttpBinding"
7: contract="Artech.WcfServices.Service.Interface.IEmployees"/>
8: </service>
9: </services>
10: </system.serviceModel>
11: </configuration>
最終我們通過如下的程序進行服務的寄宿。之前我們總是使用基於服務類型創建的ServiceHost進行服務寄宿,在這裏我們使用的是ServiceHost它的子類WebServiceHost。
1: using (WebServiceHost host = new WebServiceHost(typeof(EmployeesService)))
2: {
3: host.Open();
4: Console.Read();
5: }
由於我們寄宿的服務完全是基於Web的,所以和普通的Web站點沒有本質的區別。由於EmployeesService服務的GetAll和Get操作支持HTTP-GET請求,所以我們完全可以在瀏覽器中針對操作的地址發起請求,而返回的數據可以直接顯示在瀏覽器上。下圖所示的是通過瀏覽器調用GetAll操作(https://127.0.0.1:3721/employees/all)得到的結果,我們可以看到所有員工的列表以XML的形式返回。
我們也可以通過瀏覽器調用Get操作並直接通過在地址中指定員工的ID(https://127.0.0.1:3721/employees/001)並得到以XML表示的基於相應員工的信息。下圖所示XML正式ID為001的Employee對象序列化後的結果。如果在請求地址中指定一個不存在的ID(比如https://127.0.0.1:3721/employees/003),由於Get方法中指定了回複狀態為NotFound,我們會得到類似於訪問資源不存在的錯誤信息,就像訪問一個不存在的Web頁麵一樣。
上麵我們演示了通過瀏覽器以HTTP-GET方式請求操作地址的方式從而直接將返回結果呈現出來,現在我們來演示如何使用通過ChannelFactory<TChannel>創建的服務代理進行服務調用。我們首先在作為客戶端應用程序的Client項目中創建一個App.config,並定義如下的配置。
1: <configuration>
2: <system.serviceModel>
3: <behaviors>
4: <endpointBehaviors>
5: <behavior name="webBehavior">
6: <webHttp />
7: </behavior>
8: </endpointBehaviors>
9: </behaviors>
10: <client>
11: <endpoint name="employeeService"
12: address="https://127.0.0.1:3721/employees"
13: behaviorConfiguration="webBehavior"
14: binding="webHttpBinding"
15: contract="Artech.WcfServices.Service.Interface.IEmployees"/>
16: </client>
17: </system.serviceModel>
18: </configuration>
如上麵的配置片斷所示,我們定義了一個與服務端相匹配的客戶端終結點,該終結點上應用了一個WebHttpBehavior終結點行為。WebHttpBehavior可以說是整個Web HTTP編程模型的核心,絕大部分針對Web的支持都是通過該行為實現的。實際上服務端終結點通過WebServiceHost應用了這個終結點行為。
1: using(ChannelFactory<IEmployees> channelFactory = new ChannelFactory<IEmployees>("employeeService"))
2: {
3: IEmployees proxy = channelFactory.CreateChannel();
4:
5: Console.WriteLine("所有員工列表:");
6: Array.ForEach<Employee>(proxy.GetAll().ToArray(),employee=>Console.WriteLine(employee));
7:
8: Console.WriteLine("\n添加一個新員工(003):");
9: proxy.Create(new Employee
10: {
11: Id = "003",
12: Name = "王五",
13: Grade = "G9",
14: Department = "行政部"
15: });
16: Array.ForEach<Employee>(proxy.GetAll().ToArray(),employee => Console.WriteLine(employee));
17:
18: Console.WriteLine("\n修改員工(003)信息:");
19: proxy.Update(new Employee
20: {
21: Id = "003",
22: Name = "王五",
23: Grade = "G11",
24: Department = "銷售部"
25: });
26: Array.ForEach<Employee>(proxy.GetAll().ToArray(), employee => Console.WriteLine(employee));
27: Console.WriteLine("\n刪除員工(003)信息:");
28:
29: proxy.Delete("003");
30: Array.ForEach<Employee>(proxy.GetAll().ToArray(), employee => Console.WriteLine(employee));
31: }
服務調用程序如上所示,我們模擬了員工的添加、修改和刪除。程序之後會在客戶端控製台產生如下的輸出。
所有員工列表:
1: 所有員工列表:
2: ID: 001 姓名: 張三 級別: G7 部門: 開發部
3: ID: 002 姓名: 李四 級別: G6 部門: 人事部
4:
5: 添加一個新員工(003):
6: ID: 001 姓名: 張三 級別: G7 部門: 開發部
7: ID: 002 姓名: 李四 級別: G6 部門: 人事部
8: ID: 003 姓名: 王五 級別: G9 部門: 行政部
9:
10: 修改員工(003)信息:
11: ID: 001 姓名: 張三 級別: G7 部門: 開發部
12: ID: 002 姓名: 李四 級別: G6 部門: 人事部
13: ID: 003 姓名: 王五 級別: G11 部門: 銷售部
14:
15: 刪除員工(003)信息:
16: ID: 001 姓名: 張三 級別: G7 部門: 開發部
17: ID: 002 姓名: 李四 級別: G6 部門: 人事部
從編程角度來看,我們采用與SOAP服務完全一樣的服務調用方式,那麼如何反映出服務調用基於Web的本質呢?首先,之前我們能夠通過瀏覽器訪問GetAll和Get兩個操作可以證明這兩個服務操作是基於HTTP-GET的,返回的數據直接以單純的XML返回,並沒有封裝成SOAP。為了證明Create、Update和Delete也是完全基於Web的,我們可以通過Fiddler來分析HTTP請求的內容。
如下所示的三段XML片斷分別對應著針對上述三個服務操作調用的HTTP請求消息,從這我們可以看出它們就是單純的針對PUT、POST和DELETE方法的HTTP請求,而傳輸給服務端的數據直接作為消息的主體,並沒有封裝成SOAP消息。
1: Create:
2: PUT http://jinnan-pc:3721/employees/ HTTP/1.1
3: Content-Type: application/xml; charset=utf-8
4: Host: jinnan-pc:3721
5: Content-Length: 187
6: Expect: 100-continue
7: Accept-Encoding: gzip, deflate
8:
9: <Employee xmlns="https://www.artech.com/" xmlns:i="https://www.w3.org/2001/XMLSchema-instance"><Department>銷售部</Department><Grade>G11</Grade><Id>003</Id><Name>王五</Name></Employee>
10:
11: Update:
12: POST http://jinnan-pc:3721/employees/ HTTP/1.1
13: Content-Type: application/xml; charset=utf-8
14: Host: jinnan-pc:3721
15: Content-Length: 186
16: Expect: 100-continue
17: Accept-Encoding: gzip, deflate
18:
19: <Employee xmlns="https://www.artech.com/" xmlns:i="https://www.w3.org/2001/XMLSchema-instance"><Department>行政部</Department><Grade>G9</Grade><Id>003</Id><Name>王五</Name></Employee>
20:
21: Delete:
22: DELETE http://jinnan-pc:3721/employees/003 HTTP/1.1
23: Content-Type: application/xml; charset=utf-8
24: Host: jinnan-pc:3721
25: Content-Length: 80
26: Expect: 100-continue
27: Accept-Encoding: gzip, deflate
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:04:23