2012年11月8日 星期四

建立 RESTful WCF 服務網站

什麼是 REST

REST (Representational State Transfer, 表徵狀態轉移)

REST 是一種軟體架構風格,希望網路上資源都可以用 URL 來指定,讓客戶端可以直接透過 URL 對資源進行 CRUD (創建、獲取、修改、刪除)操作。 RE 是 Representational 的縮寫,它代表由 Client 端透過 URI 取得的資源的具體象徵 (Representational), 應用程式可以依據取得的具體象徵來轉變其狀態(在瀏覽器上就是畫面的變化), 這樣不斷的反覆過程就是所謂的 Representational State Transfer。 維基百科:REST

JSON (Javascript Object Notation)

JSON 是一種以純文字為基礎的資料格式。它也是 REST 服務預設的訊息回應格式。 因為它的結構簡單,很適合用來做為程式溝通或交換資料時使用。 也由於它的輕量化和易於閱讀的特性,目前已廣泛應用於各種技術領域,例如:AJAX, WCF, jQuery。

你可以透過特定的格式去儲存任何資料(字串,數字,陣列,物件),也可以透過物件或陣列來傳送較複雜的資料。 底下是一個簡單的 JSON 範例:

{
  "proudctName": "Computer Monitor",
  "price": "229.00",
  "specifications": 
  {
    "size": 22,
    "type": "LCD",
    "colors": ["black", "red", "white"]
  }
}

建立 REST WCF Service

用來建置 Windows Communication Foundation (WCF) 服務與用戶端應用程式時所需的型別,都包含在 System.ServiceModel 命名空間。 底下幾個相關的命名空間:

要設計 REST 服務,必須引用 System.ServiceModel.Web 命名空間。
該命名空間定義在 System.ServiceModel.Web.dll 組件裡,必須手動加入參考。
必須注意的是,Framework 版本必須選用完整版(如3.5或4.0 full profile),若選用 client profile 會找不到這個 dll。

建立支援 REST 的 WCF 服務

由於.NET 3.5 的支援,我們可以透過相當簡單的方式來建立 REST 的 WCF 服務,若開發人員想透過WCF技術,來開發 RESTful 的 Service,只需要在VS2008 SP1的專案範本中,選擇 「AJAX-enabled WCF Service」 即可:

「AJAX-enabled WCF Service」項目的內容

底下是一個 「AJAX-enabled WCF Service」的簡單範例,當新增一個 「AJAX-enabled WCF Service」項目時,下面幾點注意事項:

  • 會自動加入 AspNetCompatibilityRequirements 屬性。
    若設定成允許 ASP.NET相容性模式,則 WCF 服務會與 ASP.NET 使用相同的管線,類似於 ASMX 服務的方式。 在此模式下, WCF 服務也可提供 ASP.NET 檔案授權、 UrlAuthorization 和 HTTP 工作階段狀態等功能。
  • 預設的 ResponseFormatWebMessageFormat.Json
    若要傳回 XML ,必須加入 [WebGet(ResponseFormat=WebMessageFormat.Xml)] 屬性宣告。
  • WebInvoke :這屬性可用來設定 HTTP 動詞(例如:POST, PUT, DELETE),預設使用 HTTP POST 方式叫用。
  • WebGet :這屬性是用來設定使用 HTTP GET 叫用。這種方法的回傳結果支援快取。
  • 在第3行的 Namespace 中要設定一個值。
namespace WcfService2
{
    [ServiceContract(Namespace = "WcfService2")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class HRService
    {
        //一個方法若沒有特別宣告屬性,預設值有:
        //1. 必須使用 HTTP POST 叫用
        //2. 回傳 Json 格式
        
        [OperationContract]
        public Employee Employee1(string EmpID)
        {
            Employee emp = new Employee() { EmployeeID = EmpID, FirstName = "vito", LastName = "shao", Age = 30 };
            return emp;
        }

        [OperationContract]
        [WebGet]
        public Employee Employee1(string EmpID)
        {
            Employee emp = new Employee() { EmployeeID = EmpID, FirstName = "vito", LastName = "shao", Age = 30 };
            return emp;
        }

        [OperationContract]
        [WebGet]
        public Employee Employee2(string EmpID)
        {
            Employee emp = new Employee() { EmployeeID = EmpID, FirstName = "vito", LastName = "shao", Age = 30 };
            return emp;
        }
    }

    public class Employee
    {
        public string EmployeeID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
}

「AJAX-enabled WCF Service」項目的組態設定

在這個設定內容裡,與前一節中的 WCF 服務最大的不同在於:

  • 端點行為中加入 enableWebScript 屬性。它表示這個 endpoint 是一個支援 JSON 資料格式的 RESTful 服務,因此可以透過 AJAX 直接叫用。
  • 端點的繫結方式採用 webHttpBinding。它指出該服務必須透過 HTTP 呼叫,而不是透過 SOAP 呼叫。
<configuration>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfService2.HRServiceAspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <services>
      <service name="WcfService2.HRService">
        <endpoint address="" 
                  binding="webHttpBinding" 
                  contract="WcfService2.HRService" 
                  behaviorConfiguration="WcfService2.HRServiceAspNetAjaxBehavior"/>
      </service>
    </services>
  </system.serviceModel>

</configuration>

叫用 REST WCF Service

透過 ASP.NET AJAX ScriptManager 叫用 REST WCF Service

不曉得為什麼,在 ScriptManager 底下叫用 WCF 服務,該服務合約的 namespace 屬性值就必須指定為類別的命名空間,才可以成功呼叫。

namespace WcfService2
{
    [ServiceContract(Namespace = "WcfService2")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class HRService
    {
       
    }
}

其用戶端的叫用方法如下:

<head id="Head1" runat="server">
    <script language="javascript" type="text/javascript">
        function Button1_onclick() {
            var service = new WcfService2.HRService();
            var resutl = service.GetEmployee('001', onSuccess, onFail, null);
        }
        function onSuccess(result) {
            alert(result.FirstName + ' ' + result.LastName);
        }
        function onFail(result) {
            alert(result);
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Services>         
                <asp:ServiceReference Path="HRService.svc" />       
            </Services> 
        </asp:ScriptManager>

        <input id="Button1" type="button" value="invoke .svc with ajax" onclick="return Button1_onclick()" />
    </div>
    </form>
</body>

透過 jQuery 叫用 REST WCF Service

很奇怪,若使用 jQuery 叫用,該服務合約就不用指定 namespace 屬性。

其用戶端的叫用方法如下:

<head id="Head1" runat="server">
    <title></title>
    <script type="text/javascript" src="./Scripts/jquery-1.8.2.js"></script>

    <script language="javascript" type="text/javascript">
        function btnAjax_Post_onclick() {
            jQuery.support.cors = true;

            var empid = $("#txtEmpID").val();
            var data = '{"EmpID":"' + empid + '"}';
            var uri = "http://localhost:30302/HRService.svc/Employee1";

            $.ajax({
                type: "POST",
                url: uri,
                dataType: "json",
                data: data,
                contentType: "application/json; charset=utf-8",
                success: OnSuccess,
                error: OnFail
            });
        }

        function btnAjax_Get_onclick() {
            jQuery.support.cors = true;
            var empid = $("#txtEmpID").val();
            var uri = "http://localhost:30302/HRService.svc/Employee2?EmpID=" + empid;

            $.ajax({
                type: "GET",
                url: uri,
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                success: OnSuccess,
                error: OnFail
            });
        }
        function OnSuccess(result) {
            alert(result.d.EmployeeID + ' ' + result.d.FirstName + ' ' + result.d.LastName);
        }
        function OnFail(result) {
            alert('Service call failed: ' + result.status + '' + result.statusText);
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        EmpID: <input id="txtEmpID" type="text" value="007" /><br /><br />

        <input id="btnAjax_Post" type="button" value="invoke .svc with ajax post" onclick="return btnAjax_Post_onclick()"/><br /><br />

        <input id="btnAjax_Get" type="button" value="invoke .svc with ajax get" onclick="return btnAjax_Get_onclick()" /><br /><br />
    </div>
    </form>
</body>

底下是幾個 jQuery 用來叫用服務的方法:

  • jQuery.ajax :Perform an asynchronous HTTP (Ajax) request.
  • jQuery.getJSON :Load JSON-encoded data from the server using a HTTP GET request.
  • jQuery.post :Load data from the server using a HTTP POST request.
  • jQuery.get :Load data from the server using a HTTP GET request.
jQuery.ajax( url [, settings] )

jQuery.getJSON( url [, data] [, success(data, textStatus, jqXHR)] )
//Returns: jqXHR

jQuery.post( url [, data] [, success(data, textStatus, jqXHR)] [, dataType] )
//Returns: jqXHR

jQuery.get( url [, data ] [, success(data, textStatus, jqXHR) ] [, dataType ] )
//Returns: jqXHR

要注意一點,在 jQuery 1.4X 以後,叫用 jQuery 執行方法時,預設就不允許 cross-site 。如果要執行跨網站的方法,必須加入以下程式碼:

jQuery.support.cors = true;

套用 UriTemplate 叫用 REST WCF Service

使用 webHttp 端點行為

要使用 UriTemplate 必須使用 <webHttp> 端點行為,而不是使用 <enableWebScript> 端點行為。

<configuration>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfService3.HRServiceAspNetAjaxBehavior">
          <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <services>
      <service name="WcfService3.HRService">
        <endpoint address="" 
                  binding="webHttpBinding" 
                  contract="WcfService3.HRService" 
                  behaviorConfiguration="WcfService3.HRServiceAspNetAjaxBehavior"/>
      </service>
    </services>
  </system.serviceModel>

</configuration>

定義 UriTemplate

UriTemplate 是指這一個 RESTful 服務的呼叫方式(嚴格說起來是描述該資源的Uri位置),是要以何種 Uri 來表達,透過這種方式建立出來的 WCF 服務,就直接支援了 REST 型態的遠端呼叫方式。 例如,上例中我們給定的『UriTemplate』是"HR/EmpId/{EmpID}",則這個資源可以透過底下的網址來取得:
http://localhost:30303/HRService.svc/HR/EmpId/001

namespace WcfService3
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class HRService
    {
        [OperationContract]
        [WebInvoke(
            Method = "POST",
            UriTemplate = "HR/EmpId/{EmpID}",
            RequestFormat = WebMessageFormat.Json, 
            ResponseFormat = WebMessageFormat.Json, 
            BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        public Employee Employee1(string EmpID)
        {
            Employee emp = new Employee() { EmployeeID = EmpID+"post", FirstName = "vito", LastName = "shao", Age = 30 };
            return emp;
        }

        [OperationContract]
        [WebGet(
            UriTemplate = "HR/EmpId/{EmpID}",
            RequestFormat = WebMessageFormat.Json, 
            ResponseFormat = WebMessageFormat.Json, 
            BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        public Employee Employee2(string EmpID)
        {
            Employee emp = new Employee() { EmployeeID = EmpID + "get", FirstName = "vito", LastName = "shao", Age = 30 };
            return emp;
        }
    }

    public class Employee
    {
        public string EmployeeID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
}

使用 jQuery 方法叫用服務

      <script language="javascript" type="text/javascript">            function btnAjax_Post_onclick() {            jQuery.support.cors = true;
              var empid = $("#txtEmpID").val();              var data = '{"EmpID":"' + empid + '"}';              var uri = "http://localhost:30303/HRService.svc/HR/EmpId/" + empid;                $.ajax({                  type: "POST",                  url: uri,                  dataType: "json",                  data: data,                  contentType: "application/json; charset=utf-8",                  success: OnSuccess,                  error: OnFail              });          }            function btnAjax_Get_onclick() {            jQuery.support.cors = true;
            var empid = $("#txtEmpID").val();              var uri = "http://localhost:30303/HRService.svc/HR/EmpId/" + empid;                $.ajax({                  type: "GET",                  url: uri,                  dataType: "json",                  contentType: "application/json; charset=utf-8",                  success: OnSuccess,                  error: OnFail              });          }            function btnPost_onclick() {            jQuery.support.cors = true;
            var empid = $("#txtEmpID").val();              var uri = "http://localhost:30303/HRService.svc/HR/EmpId/" + empid;                $.post(uri, null, OnSuccess);          }            function btnGet_onclick() {            jQuery.support.cors = true;
            var empid = $("#txtEmpID").val();              var uri = "http://localhost:30303/HRService.svc/HR/EmpId/" + empid;                $.get(uri, null, OnSuccess);          }            function OnSuccess(result) {              alert(result.EmployeeID + ' ' + result.FirstName + ' ' + result.LastName);          }          function OnFail(result) {              alert('Service call failed: ' + result.status + '' + result.statusText);          }        </script>  

沒有留言:

張貼留言