WCF REST 入門套件的開發人員指南

Aaron Skonnard、Pluralsight

2009 年 8 月

注意:本檔是以 WCF REST 入門套件 Preview 2 版本為基礎。

概觀

Windows Communication Foundation (WCF) 3.5 引進了在 .NET 中建置 RESTful 服務的「Web」程式設計模型。 雖然 WCF 3.5 為建置各種 RESTful 服務提供穩固的基礎,但仍需要開發人員針對其建置的每個 RESTful 服務實作大量的未定板程式碼,並直接處理重要的 HTTP 通訊協定功能。 WCF REST 入門套件提供一組 WCF 延伸模組和專案範本,旨在進一步簡化 REST 開發。 雖然 WCF REST 入門套件目前被視為「預覽」技術,但其許多功能很可能會找到未來版本的.NET Framework。

在此白皮書中,我們將完整探索各種 WCF REST 入門套件功能,並示範如何開始使用它們來處理現今最常見的 REST 開發案例。 Microsoft 致力於在 Microsoft .NET 中提供豐富的 RESTful 服務平臺。

如果您不熟悉 REST 概念或 WCF 3.5 REST 程式設計模型,請務必閱讀使用 WCF 3.5 設計和建置 RESTful 服務的指南,再繼續進行。

WCF REST 入門套件簡介

自 WCF 3.5 發行以來,Microsoft 一直努力在 .NET 平臺上建置和取用 RESTful 服務。 該工作的結果之一是一套新的協助程式類別、擴充方法和 Visual Studio 專案範本,封裝到 WCF REST Starter Kit 中。 現在,您可以從 CodePlex 下載 WCF REST 入門套件,但其許多功能可能會發現其未來版本的正式 .NET Framework。 您可以在 MSDN WCF REST 登陸頁面上找到 WCF REST 入門套件的最新資訊。

WCF REST Starter Kit (Preview 2) 隨附三個新的 .NET 元件,您可以利用程式碼來簡化常見的 REST 程式設計工作, (請參閱圖 1) 。

組件名稱 Description

Microsoft.ServiceModel.Web

包含一組新的類別和擴充方法,可簡化使用 WCF 建置和裝載 RESTful 服務的程式。

Microsoft.Http

包含一組新的類別和擴充方法,可簡化使用 HTTP 取用 RESTful 服務的程式。

Microsoft.Http.Extensions

包含一組新的類別和擴充方法,用於取用特定類型的 RESTful 服務和回應格式。

圖 1:WCF REST 入門套件元件

Microsoft.ServiceModel.Web 元件包含新的 WebServiceHost2 類別 (衍生自 WCF 3.5 中的 WebServiceHost) 專為裝載 RESTful 服務而設計。 這個類別會啟動數個 REST 特定功能,並以最終讓您的 RESTful 服務更容易建置及更容易供其他人取用的方式設定基礎 WCF 執行時間。 這個新元件也隨附一些 .NET 屬性和擴充方法,您也可以在程式碼中利用這些方法。 這些延伸模組可讓您點選 WebServiceHost2 所提供的各種功能。

Microsoft.Http 元件包含新的用戶端 HTTP API,用於取用 RESTful 服務。 此元件中感興趣的主要類別是 HttpClient。 透過 HttpClient,您可以輕鬆地發出 HTTP GET、POST、PUT 和 DELETE 要求,並透過各種不同的內容特定 API 處理回應。 它也可讓您更輕鬆地傳送表單資料和查詢字串值,並簡化透過一組新具型別標頭類別使用 HTTP 標頭的程式。 這個新的用戶端 API 提供更自然的 HTTP 體驗,以取用網路上找到的任何 RESTful 服務。

最後,Microsoft.Http.Extensions 元件包含一些著重于特定案例的特殊 HttpClient 衍生類別。 它也提供相當多的擴充方法,著重于處理各種不同格式的 HTTP 訊息主體, (XML、JSON、Atom 摘要等) 。 取用服務時,您將利用這些擴充方法與 HttpClient 搭配使用。

WCF REST 入門套件也隨附一組實用的 Visual Studio 專案範本, (請參閱圖 2) 以常見 REST 案例為目標。 這些專案範本提供您開始使用所需的重複使用程式碼,利用上述元件中找到的新類別/延伸模組。 例如,有一個範本可產生「單一」服務, (公開單一資源) ,另一個用於產生「集合」服務, (公開資源集合) 。 有另一個範本可用來產生 Atom 摘要,另一個範本會產生功能完整的 AtomPub 服務。 這些範本可協助啟動這些不同 REST 案例的服務實作。

專案範本 Description

REST 單一服務

產生服務,定義範例單一資源 (SampleItem) ,以及與 SINGLEton (GET、POST、PUT 和 DELETE) 互動的完整 HTTP 介面,同時支援 XML 和 JSON 標記法。

REST 集合服務

與 REST 單一服務類似,它也支援管理 SampleItem 資源的集合。

Atom 摘要服務

產生服務,此服務會公開具有虛擬資料的範例 Atom 摘要。

AtomPub 服務

產生功能完整的 AtomPub 服務,能夠管理資源集合以及媒體專案。

HTTP 純文字 XML 服務

使用簡單的 GET 和 POST 方法來產生服務,您可以針對未完全符合 RESTful 設計原則的純舊 XML (POX) 服務建置,而是只依賴 GET 和 POST 作業。

圖 2:WCF REST 入門套件專案範本

在後續各節中,我們將更詳細地探討這些元件和專案範本,以及我們將探討如何處理一些常見的 REST 案例。

使用 Microsoft.ServiceModel.Web 建置和裝載服務

若要開始利用 WCF 服務專案中的 WCF REST 入門套件,您必須修改主應用程式,以使用 Microsoft.ServiceModel.Web 中找到的 WebServiceHost2 類別。 這個類別會針對主應用程式公開的所有服務端點啟動新的 WCF REST 入門套件功能。 完成此動作之後,您就可以開始利用自動說明頁面、HTTP 快取支援、新的例外狀況處理行為,以及新的要求攔截功能。 首先,我將示範如何連接 WebServiceHost2,然後我們將探索每個新功能區域。

使用 WebServiceHost2 裝載 REST 服務

WebServiceHost2 類別衍生自 WCF 3.5 中找到的 WebServiceHost 類別。 因此,您可以使用它,就像任何其他 ServiceHost 衍生類別一樣。 如果您在主應用程式中使用自我裝載技術,您可能有一些程式碼現在看起來像這樣:

WebServiceHost host = new WebServiceHost(typeof(BookmarkService));
host.Open();

若要開始搭配服務使用 WCF REST 入門套件功能,您只需要在程式碼中將類別名稱從 「WebServiceHost」 變更為 「WebServiceHost2」 即可:

WebServiceHost2 host = new WebServiceHost2(typeof(BookmarkService));
host.Open();

您也可以在 IIS 中裝載服務時利用 WebServiceHost2。  如果您目前在 IIS 內裝載 WCF 服務,您將會有一個 SVC 檔案,看起來像這樣:

<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

WCF REST 入門套件隨附新的 WebServiceHost2Factory 類別 – 負責啟動 WebServiceHost2 實例。 只要將 Factory 類別取代為 WebServiceHost2Factory,IIS 裝載的服務將會自動由 WebServiceHost2 實例管理:

<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
    Factory="Microsoft.ServiceModel.Web.WebServiceHost2Factory"%>

那麼,WebServiceHost2 與 WCF 3.5 隨附的 WebServiceHost 類別有何不同?  它會執行兩個重要動作。  首先,它會以 WebHttpBehavior2 實例取代所有端點上的 WebHttpBehavior。 新的 WebHttpBehavior2 類別負責提供自動說明頁面功能和伺服器 「Web」 錯誤處理邏輯。 其次,它會將新的繫結項目新增至每個端點,以插入新的要求攔截邏輯。 因此,只要變更主機類型,您的 WCF REST 服務就能夠利用這些新功能。

自動說明頁面

使用 WebServiceHost2 之後,您的服務將會自動享有新的自動說明頁面功能的優點,這是 RESTful 服務的一大步。 您可以流覽至服務基底位址並附加至結尾 (請參閱圖 3) ,以查看說明頁面。

說明頁面會針對以 [WebGet] 或 [WebInvoke] 標注的每個 WCF 服務作業提供人類可閱讀的描述,並針對每個服務作業提供描述 URI 範本、支援的 HTTP 作業,以及要求/回應格式,基本上是取用者需要知道的所有專案。

針對每個要求/回應,說明頁面也會提供 XML 架構和對應的範例 XML 實例,供取用者用來與服務整合。 取用者可以使用架構來產生適當的用戶端可序列化類型,或者他們只要檢查範例 XML 檔即可手動判斷如何撰寫適當的 XML 處理常式代碼。 這兩種方法都很有用。

圖 3:RESTFul 服務的自動說明頁面

請務必注意,說明頁面會以 Atom 摘要的形式傳回。 大部分的網頁瀏覽器都提供內建摘要轉譯,以協助人類檢視,這是 Internet Explorer 在圖 中執行的動作。 不過,由於它是摘要,取用者也可以視需要以程式設計方式取用描述。 如果您要關閉 Internet Explorer 選項中的「摘要閱讀檢視」,您實際上會看到轉譯的摘要 XML,您也可以檢視頁面的來源來檢查摘要 XML。

根據預設,說明頁面會在基底位址上使用,並將 「help」 附加至結尾,但您可以透過 WebServiceHost2 上的 HelpPageLink 屬性自訂說明頁面位址。

您也可以透過 Microsoft.ServiceModel.Web 中找到的新 [WebHelp] 屬性,將人類可讀的描述新增至每個 RESTful 作業,如下列範例所示:

[WebHelp(Comment = "Returns the user account details for the authenticated user.")]
[WebGet(UriTemplate = BookmarkServiceUris.User)]
[OperationContract]
User GetUserAsXml(string username)
{
    return HandleGetUser(username);
}

現在當您重新執行主應用程式並流覽回說明頁面時,您會看到此註解文字出現在 GetUserAsXml 描述中, (請參閱圖 4) 。

圖 4:具有自訂描述的自動說明頁面

這個新的說明頁面會自動讓您的 RESTful 服務更容易探索,最終讓其他人更容易取用。 您的取用者可以探索服務的 URI 設計、支援的 HTTP 作業和要求/回應格式,而您的描述一律會與您的 WCF 程式碼保持同步,類似于使用 ASP.NET Web 服務的方式。

您仍然不會在 la WSDL) (取得完整的用戶端程式代碼產生體驗,但當您將此說明頁面與新的 HttpClient 功能結合時,您真的不需要它。

ExceptionHandling

實作 RESTful 服務的其中一個較繁瑣層面是直接處理一些 HTTP 通訊協定詳細資料,例如傳回適當的 HTTP 狀態碼和描述,特別是在 WCF 服務作業的內容中。 下列程式碼說明如何檢查未經授權的使用者,並在必要時傳回 401「未經授權」回應:

if (!IsUserAuthorized(username))  {
    WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.Unauthorized;
    WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
    return null;
}

此程式碼並不困難,但也不會傳回詳細的回應訊息,這通常對取用者很有説明。 當您想要傳回這種類型的詳細回應訊息時,複雜度會大幅增加,因為 WCF 作業中沒有簡單的方法。

為了簡化此常見案例,WCF REST 入門套件提供新的 WebProtocolException 類別,讓呼叫端能夠輕鬆地傳回 HTTP 錯誤。 您只需在 WCF 服務作業內擲回 WebProtocolException 的實例、指定 HTTP 狀態碼和錯誤訊息,並 (假設您使用 WebServiceHost2) 基礎執行時間會負責產生適當的 HTTP 回應訊息。

下列範例說明如何擲回幾個不同的 WebProtocolException 實例,以指定不同的 HTTP 狀態碼和錯誤訊息:

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized,
        "Missing or invalid user key (supply via the Authorization header)", null);
}
if (bookmark_id <= 0)
    throw new WebProtocolException(HttpStatusCode.BadRequest,
        "The bookmark_id field must be greater than zero", null);

擲回 WebProtocolException 之後,WebHttpBehavior2) 將處理自訂 WCF 錯誤處理常式 (。 錯誤處理常式會將 WebProtocolException 實例轉譯為適當的 HTTP 回應訊息,其中包含取用者權益的詳細回應。

圖 5 說明在瀏覽器中轉譯時,第一個 WebProtocolException 的外觀。 請注意產生的 XHTML 如何清楚顯示 HTTP 狀態碼以及「詳細資料」訊息。 此標準 XHTML 範本內建于 WebProtocolException 類別中,因此如果您想要運作的方式,您不需要進一步執行任何動作,而且您的取用者會收到合理的動作。

圖 5:在瀏覽器中呈現的 WebProtocolException

不過,如果您想要自訂產生的 XHTML,您可以使用其中一個其他建構函式多載,並提供您想要傳回的精確 XHTML,如下所示:

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized, "Unauthorized",
        new XElement("html",
            new XElement("body"),
                new XElement("h1", "Unauthorized"),
                new XElement("p", "Missing or invalid user key " +
                "(supply via the Authorization header)")), true, null);
}

圖 6 顯示在瀏覽器中轉譯時擲回此 WebProtocolException 的結果:

圖 6:使用自訂 XHTML 回應的 WebProtocolException

此方法可讓您完全自由回應 XHTML。

不過,如果您不擔心在 XHTML) 中傳回人類可讀取的錯誤訊息 (,您可以改為傳回自訂類型,以使用 DataContractSerializer) 序列化成 XML (。 下列程式碼範例說明如何使用名為 CustomErrorMessage 的自訂類型來完成此作業:

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized,
        "Custom error message",
        new CustomErrorMessage() {
            ApplicationErrorCode = 5000,
            Description = "Authentication token missing" },
        null);
}

在此情況下,取用者會收到圖 7 中說明的自訂 XML 訊息。

圖 7:使用自訂 XML 回應的 WebProtocolException

此外,如果 WCF 作業指定了回應的 WebMessageFormat.Json,產生的 XML 將會使用 DataContractJsonSerializer 序列化,以便將 JSON 傳回給取用者。

您甚至可以藉由整合 ASP.NET 自訂錯誤功能,進一步採取一個步驟。 您可以將一些程式碼新增至 Global.asax 以檢查 WebProtocolException 是否存在,並在找到 WebProtocolException 時將取用者重新導向至自訂錯誤頁面來完成此動作:

protected void Application_EndRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Error != null)
    {
        WebProtocolException webEx =
            HttpContext.Current.Error as WebProtocolException;
        if (webEx != null && webEx.StatusCode == HttpStatusCode.BadRequest)
        {
            HttpContext.Current.ClearError();
            HttpContext.Current.Response.Redirect("BadRequest.htm");
        }
        if (webEx != null && webEx.StatusCode == HttpStatusCode.Unauthorized)
        {
            HttpContext.Current.ClearError();
            HttpContext.Current.Response.Redirect("Unauthorized.htm");
        }
    }
}

WCF REST 入門套件 SDK 中有兩個範例說明這些 WebProtocolException 功能的運作方式–一個稱為 「WebException」 ,另一個稱為 「WebException2」。 最後,這些功能可讓您更輕鬆地藉由擲回例外狀況來產生包含描述性回應訊息的自訂 HTTP 錯誤訊息,這是更自然的 .NET 開發人員模型。

快取支援

REST 的主要潛在優點之一是 HTTP 快取。 不過,為了瞭解該優點,您必須利用要求和回應訊息中的各種 HTTP 快取標頭。 您可以透過 WebOperationCoNtext 實例手動存取要求/回應標頭,以在 WCF 服務作業內達成此目的,但無法正常執行。

WCF REST 入門套件也提供更簡單的模型,可讓您透過 [WebCache] 屬性控制快取,以宣告方式套用至各種 GET 作業。 此屬性可讓您為每個作業指定快取設定檔,然後快取行為 (CacheingParameterInspector) 負責處理所有基礎 HTTP 快取詳細資料。

[WebCache] 實作是以 ASP.NET 輸出快取為基礎,並提供在 System.Web.UI.OutputCacheParameters 類別上找到的大部分相同屬性。 它只會將相同的行為與 WCF 執行時間整合,並輕鬆地以宣告方式將該行為套用至 WCF 服務作業。 下列範例示範如何快取 [WebGet] 回應 60 秒:

[WebCache(Duration = 60)]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

不過,由於此作業可以針對每個提供的「標記」傳回不同的回應,因此我們真的想要根據標記來變更快取的回應。 您可以使用 VaryByParam 屬性 (,從 ASP.NET) 熟悉此屬性,如下所示:

[WebCache(Duration = 60, VaryByParam = "tag")]
[WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
[OperationContract]
Bookmarks GetPublicBookmarksByTagAsXml(string tag)
{
    return HandleGetPublicBookmarks(tag);
}

除了 VaryByParam 之外,[WebCache] 屬性還提供 VaryByHeader 和 VaryByCustom,可讓您指定不同的變數來影響輸出快取專案。 您也可以使用 Location 來控制允許在 (快取回應的位置,例如 Any、Client、Server、ServerAndClient 等) ,以及防止完全快取,您可以將 NoStore 屬性設定為 「true」。

[WebCache] 也支援 CacheProfile 屬性,可讓您參考web.config中找到的「輸出快取設定檔」。例如,下列web.config包含名為 「CacheFor1Min」 的輸出快取設定檔,指定回應會快取 60 秒,快取的回應可以儲存在任何地方,而快取專案會因 「tag」 參數而有所不同:

<configuration>
  <system.web>
    <compilation debug="true"/>
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <clear/>
          <add name="CacheFor1Min" duration="60" enabled="true"
               location="Any" varyByParam="tag"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>
</configuration>

這可讓您將快取行為與已編譯的 WCF 程式碼分離。 您可以透過 [WebCache] 屬性將此輸出快取設定檔套用至 WCF 作業,如下所示:

[WebCache(CacheProfileName="CacheFor1Min")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

最後,[WebCache] 屬性甚至可讓您透過 SqlDependency 屬性將快取行為系結至 SQL 相依性。 若要利用這項功能,您必須為有問題的資料庫新增 < sqlCacheDependency > 專案,以web.config。然後使用 [WebCache] 屬性的 SqlDependency 屬性來指定快取專案應該相依的資料庫 & 資料表名稱組清單。 修改任何指定的資料表時,快取專案將會過期。

下列web.config說明如何設定新的 < sqlCacheDependency > 專案:

<configuration>
  <connectionStrings>
    <add name="bmconn" connectionString=
     "Data Source=.; Initial Catalog=BookmarksDB; Integrated Security=true" />
  </connectionStrings>
    <system.web>
    <caching>
      <sqlCacheDependency enabled="true" pollTime="1000" >
        <databases>
          <add name="bmdb" connectionStringName="bmconn" />
        </databases>
      </sqlCacheDependency>
    </caching>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>
</configuration>

然後,您可以將 [WebCache] 套用至作業,將輸出快取行為系結至 SQL 資料庫內的特定資料表。 下列範例會將輸出快取系結至 「Bookmarks」 資料表:

[WebCache(SqlDependency="bmdb:Bookmarks", VaryByParam = "tag")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

如此一來,此特定作業的輸出將會針對每個唯一的「tag」) 快取 (,直到基礎 Bookmarks 資料表中的資料變更為止。

[WebCache] 屬性可讓您更輕鬆地利用 HTTP 快取,而不需要直接使用 HTTP 快取標頭。 基礎 WCF 行為會負責在回應中插入 HTTP 快取控制、日期、到期和不同 HTTP 標頭,然後用戶端可以利用這些標頭來快取回應,並減少未來的往返次數。

WCF REST 入門套件 SDK 隨附兩個完整範例,說明如何更詳細地使用 [WebCache] 功能,其中一個稱為 「Cacheing1」,另一個名為 「Cacheing2」 – 「Cacheing2」 範例會使用 SQL 快取相依性提供完整的範例。

除了 [WebCache] 功能之外,WCF REST 入門套件也隨附幾個擴充方法,可讓您更輕鬆地使用 ETag,以簡化實作條件式 GET 和條件式 PUT 案例的程式。 我稍後會在檔中顯示此範例。

要求攔截

建置 RESTful 服務時的另一個常見需求是「要求攔截」。 例如,當您需要實作將套用至所有作業的服務行為 (時,例如驗證或自訂分派邏輯) ,通常最好是實作它做為 WCF 行為,因為行為模型可讓您將要求處理「攔截器」插入執行時間。 唯一的問題在於撰寫 WCF 行為,而攔截器相當複雜,不適用於心心的模糊。 因此,為了簡化此常見案例,WCF REST 入門套件提供更簡單的「要求攔截」機制,讓您無法撰寫更複雜的 WCF 擴充性元件。

在 Microsoft.ServiceModel.Web 中,您會發現名為 RequestInterceptor 的新抽象基類,其定義稱為 ProcessRequest 的單一抽象方法。 以下是完整的類別定義:

public abstract class RequestInterceptor
{
    protected RequestInterceptor(bool isSynchronous);
    public bool IsSynchronous { get; }
    public virtual IAsyncResult BeginProcessRequest(RequestContext context,
        AsyncCallback callback, object state);
    public virtual RequestContext EndProcessRequest(IAsyncResult result);
    public abstract void ProcessRequest(ref RequestContext requestContext);
}

如果您想要支援非同步呼叫) ,您可以從 RequestInterceptor 衍生類別,並覆寫 ProcessRequest (和 BeginProcessRequest/EndProcessRequest。 ProcessRequest 的實作是您實作要求攔截邏輯的位置。 請注意,您提供了要求內容實例,可讓您存取要求訊息,並提供一些方法來縮短要求管線並傳迴響應消息。

WebServiceHost2 類別會管理針對特定服務所設定的 RequestInterceptor 實例集合。 您只要在主機實例上呼叫 Open 之前,先將 RequestInterceptor 實例新增至攔截器集合。 然後,當您呼叫 Open 時,它們會在幕後插入要求處理管線, (透過新的繫結項目) 。

下列範例示範如何實作 RequestInterceptor,以執行 API 金鑰驗證並拒絕未經授權的要求:

public class AuthenticationInterceptor : RequestInterceptor
{
    public AuthenticationInterceptor() : base(false) { }
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (!IsValidApiKey(requestContext))
            GenerateErrorResponse(requestContext,
                HttpStatusCode.Unauthorized,
                "Missing or invalid user key (supply via the Authorization header)");
    }
    public bool IsValidUserKey(Message req, string key, string uri)
    {
        ... // ommitted for brevity
    }
    public void GenerateErrorResponse(RequestContext requestContext,
        HttpStatusCode statusCode, string errorMessage)
    {
        // The error message is padded so that IE shows the response by default
        string errorHtml =
            "<html><HEAD><TITLE>Request Error</TITLE></HEAD><BODY>" +
            "<H1>Error processing request</H1><P>{0}</P></BODY></html>";
        XElement response = XElement.Load(new StringReader(
            string.Format(errorHtml, errorMessage)));
        Message reply = Message.CreateMessage(MessageVersion.None, null, response);
        HttpResponseMessageProperty responseProp = new HttpResponseMessageProperty()
        {
            StatusCode = statusCode
        };
        responseProp.Headers[HttpResponseHeader.ContentType] = "text/html";
        reply.Properties[HttpResponseMessageProperty.Name] = responseProp;
        requestContext.Reply(reply);
        // set the request context to null to terminate processing of this request
        requestContext = null;
    }
}

現在,您可以撰寫自訂 ServiceHostFactory,以利用此要求攔截器搭配 IIS 裝載的服務,以在第一次建立 WebServiceHost2 實例時插入攔截器。 下列範例說明如何完成此作業:

public class SecureWebServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        WebServiceHost2 host = new WebServiceHost2(serviceType, true, baseAddresses);
        host.Interceptors.Add(new AuthenticationInterceptor());
        return host;
    }
}

然後,您只需在 .svc 檔案中指定 SecureWebServiceHostFactory, (使用 Factory 屬性) ,您的攔截器會自動啟動。 這比撰寫相等的 WCF 行為、攔截器和屬性類別更為簡單,這需要提取此動作。

WCF REST 入門套件 SDK 隨附一些額外的範例,說明 RequestInterceptor 的一些可能性。 其中一個範例示範如何使用 RequestInterceptor 來實作 X-HTTP-Method-Override 行為。 以下是 RequestInterceptor 實作:

public class XHttpMethodOverrideInterceptor : RequestInterceptor
{
    public XHttpMethodOverrideInterceptor() : base(true) {}
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null || requestContext.RequestMessage == null)
        {
            return;
        }
        Message message = requestContext.RequestMessage;
        HttpRequestMessageProperty reqProp = (HttpRequestMessageProperty)
            message.Properties[HttpRequestMessageProperty.Name];
        string methodOverrideVal = reqProp.Headers["X-HTTP-Method-Override"];
        if (!string.IsNullOrEmpty(methodOverrideVal))
        {
            reqProp.Method = methodOverrideVal;
        }
    }
}

它會尋找 X-HTTP-Method-Override 標頭,如果找到標頭,則會將要求訊息的 HTTP 方法重設為標頭中找到的值。 這提供此案例非常簡單的解決方案,而且可以輕鬆地在所有 RESTful WCF 服務解決方案中重複使用。

他們提供 WCF REST 入門套件的另一個 RequestInterceptor 範例是以內容類型為基礎的分派。 換句話說,可以根據 HTTP 接受或內容類型要求標頭的值,分派至不同的服務作業。 下列範例示範如何透過另一個 RequestInterceptor 實作來完成此作業:

public class ContentTypeRequestInterceptor : RequestInterceptor
{
    public ContentTypeRequestInterceptor() : base(true) {}
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null) return;
        Message request = requestContext.RequestMessage;
        if (request == null) return;
        HttpRequestMessageProperty prop = (HttpRequestMessageProperty)
            request.Properties[HttpRequestMessageProperty.Name];
        string format = null;
        string accepts = prop.Headers[HttpRequestHeader.Accept];
        if (accepts != null)
        {
            if (accepts.Contains("text/xml") || accepts.Contains("application/xml"))
            {
                format = "xml";
            }
            else if (accepts.Contains("application/json"))
            {
                format = "json";
            }
        }
        else
        {
            string contentType = prop.Headers[HttpRequestHeader.ContentType];
            if (contentType != null)
            {
                if (contentType.Contains("text/xml") ||
                    contentType.Contains("application/xml"))
                {
                    format = "xml";
                }
                else if (contentType.Contains("application/json"))
                {
                    format = "json";
                }
            }
        }
        if (format != null)
        {
            UriBuilder toBuilder = new UriBuilder(request.Headers.To);
            if (string.IsNullOrEmpty(toBuilder.Query))
            {
                toBuilder.Query = "format=" + format;
            }
            else if (!toBuilder.Query.Contains("format="))
            {
                toBuilder.Query += "&format=" + format;
            }
            request.Headers.To = toBuilder.Uri;
        }
    }
}

這些只是您可以使用 RequestInterceptor 機制完成的一些範例。 您可以使用這項技術來完成各種不同的要求處理行為,例如記錄、驗證,甚至是自訂快取。 解決方案可讓您輕鬆地跨 RESTful 服務重複使用。

其他類別和擴充方法

除了我剛才描述的主要功能之外,WCF REST 入門套件也隨附各種擴充方法,可簡化常見的 REST 程式設計工作。 這些擴充方法會散佈在 Microsoft.ServiceModel.Web 內的數個類別。 我會在這裡醒目提示其中一些。

WebOperationCoNtextExtensions 類別包含核心 WebOperationCoNtext 類別的一組擴充方法。 其中一些是設計來讓 URI 和 UriTemplate 操作更容易 (GetBaseUri、GetRequestUri 和 BindTemplateToRequestUri) 。 其餘部分的設計目的是透過數個 SetHashEtag 多載和 ThrowIfEtagMissingOrStale) 簡化 HTTP ETag 處理 (。 下列顯示 WebOperationCoNtextExtensions 的類別定義:

public static class WebOperationContextExtensions
{
    public static Uri BindTemplateToRequestUri(this WebOperationContext context,
        UriTemplate template, params string[] values);
    public static Uri GetBaseUri(this IncomingWebRequestContext context);
    public static NameValueCollection GetQueryParameters(
        this IncomingWebRequestContext context);
    public static Uri GetRequestUri(this IncomingWebRequestContext context);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        BinaryFormatter formatter, T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        XmlObjectSerializer serializer, T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        XmlSerializer serializer, T entityToHash);
    public static void ThrowIfEtagMissingOrStale(
        this IncomingWebRequestContext context, string expectedEtag);
}

SerializationExtensions 類別包含數個擴充方法,可在使用 XLinq) 時,簡化對 XElement 實例 (序列化物件。 它提供數個 ToObject 和 ToXml 多載:

public static class SerializationExtensions
{
    public static TObject ToObject<TObject>(this XElement xml);
    public static TObject ToObject<TObject>(this XElement xml,
        XmlObjectSerializer serializer);
    public static TObject ToObject<TObject>(this XElement xml,
        XmlSerializer serializer);
    public static XElement ToXml<TObject>(TObject obj);
    public static XElement ToXml<TObject>(TObject obj,
        XmlObjectSerializer serializer);
    public static XElement ToXml<TObject>(TObject obj, XmlSerializer serializer);
}

最後,SyndicationExtensions 類別包含數個擴充方法,可透過 SyndicationFeed 和 SyndicationItem 類別簡化使用 RSS/Atom 摘要。 這些方法可讓您更輕鬆地將不同類型的連結新增至摘要,包括「自我」連結、「編輯」連結和導覽連結:

public static class SyndicationExtensions
{
    public static void AddEditLink(this SyndicationItem entry, Uri uri);
    public static void AddEditMediaLink(this SyndicationItem entry, Uri uri,
        string contentType, long contentLength);
    public static void AddNextPageLink(this SyndicationFeed feed, Uri uri);
    public static void AddPreviousPageLink(this SyndicationFeed feed, Uri uri);
    public static void AddSelfLink(this SyndicationFeed feed, Uri uri);
}

除了這些擴充方法之外,Microsoft.ServiceModel.Web.SpecializedServices 命名空間也包含一組服務合約介面和基類定義,適用于某些最常見的「特製化」RESTful 服務類型, (請參閱圖 8) 。 您可以在這裡找到單一服務、收集服務和 AtomPub 服務的型別。

介面 基底類別 Description

ISingletonService < TItem>

SingletonServiceBase < TItem>

定義「單一」REST 服務的一般服務合約和基底實作,例如只公開單一 TItem 資源的 RESTful 服務。

ICollectionService < TItem>

CollectionServiceBase < TItem>

定義「集合」REST 服務的一般服務合約和基底實作,例如公開 TItem 資源集合的 RESTful 服務。

IAtomPubService

AtomPubServiceBase

定義 AtomPub 服務的一般服務合約和基底實作。

圖 8:特製化 REST 服務合約介面和基類

這些類型會針對每種服務類型定義 REST 合約詳細資料,同時讓您專注于 TItem) 和核心 CRUD 功能的資源定義 (。

如果您想要實作下列其中一個標準服務類型,只要從感興趣的基類和對應的服務合約定義衍生類別即可。 然後,您可以覆寫抽象方法,以定義有問題的資源的 CRUD 功能,然後準備好裝載它。 您不需要擔心 HTTP 詳細資料,因為它們是由基底類型處理。

例如,假設您想要使用 ICollectionService < TItem 和 CollectionServiceBase < TItem >> 類型來實作簡單的書簽服務。 您可以在為資源類型指定 Bookmark 時,從這兩種類型衍生新的類別來執行此動作。 然後,您可以覆寫基類上定義的幾個抽象方法,包括 OnAddItem、OnDeleteItem、OnGetItem、OnGetItems 和 OnUpdateItem。 您的方法實作會定義 CRUD 功能。

圖 9 顯示您參考的完整範例實作。

public class BookmarkService : CollectionServiceBase<Bookmark>,
    ICollectionService<Bookmark>
{
    Dictionary<string, Bookmark> bookmarks = new Dictionary<string, Bookmark>();
    protected override Bookmark OnAddItem(Bookmark initialValue, out string id)
    {
        id = Guid.NewGuid().ToString();
        bookmarks.Add(id, initialValue);
        return initialValue;
    }
    protected override bool OnDeleteItem(string id)
    {
        bookmarks.Remove(id);
        return true;
    }
    protected override Bookmark OnGetItem(string id)
    {
        return bookmarks[id];
    }
    protected override IEnumerable<KeyValuePair<string, Bookmark>> OnGetItems()
    {
        return bookmarks;
    }
    protected override Bookmark OnUpdateItem(string id, Bookmark newValue)
    {
        bookmarks[id] = newValue;
        return bookmarks[id];
    }
}

圖 9:使用 ICollectionService < TItem 和 CollectionServiceBase < TItem > 的範例實作>

圖 9 所示的範例實作現在已完全準備好裝載。 為了簡化裝載層面,Microsoft.ServiceModel.Web.SpecializedServices 命名空間也包含每個特製化服務類型的特製化主機類別。 例如,您會在處置時找到 SingletonServiceHost、CollectionServiceHost 和 AtomPubServiceHost 類別。 這些特製化主機類型會在幕後為您設定必要的 WCF 行為,因此您不需要擔心。

下列程式碼範例說明如何在簡單主控台應用程式中裝載圖 9 所示的 BookmarkService 實作:

class Program
{
    static void Main(string[] args)
    {
        CollectionServiceHost host = new CollectionServiceHost(
            typeof(BookmarkService),
            new Uri("https://localhost:8080/bookmarkservice"));
        host.Open();
        Console.WriteLine("Host is up and running...");
        Console.ReadLine();
        host.Close();
    }
}

如果您執行此主控台應用程式,然後流覽至服務的基底位址,您應該取得 OnGetItems 傳回的書簽清單 () ,如圖 10 所示。

圖 10:正在執行時流覽至 BookmarkService

如果您將 「/help」 附加至基底 URI 的結尾,您會看到 REST 說明頁面 (查看,如果您將 「/help」 附加至基底 URI 的結尾,您會看到 REST 說明頁面 (請參閱圖 11) .) 。

圖 11:描述書簽服務的說明頁面

如果您流覽說明頁面,您會看到此服務實作支援每個邏輯作業的 XML 和 JSON 訊息格式,而不需要在我們的部分執行任何動作。

您可以使用這些相同的技術來實作單一服務, (只公開單一資源) 或功能完整的 AtomPub 服務,而目前在整個產業中變得相當熱門。 這些特製化服務類型可讓您更輕鬆地啟動並執行 RESTful 服務,假設它符合泛型實作所加加的條件約束。

為了讓開發人員更容易使用這些特製化類型,WCF REST 入門套件也隨附一組 Visual Studio 專案範本,協助啟動使用這些「特製化」服務類型來建立新服務實作的程式。

Visual Studio 專案範本

WCF REST 入門套件隨附一些實用的 Visual Studio 專案範本,可為少數類型的「特製化」服務提供必要的樣板程式碼。 安裝 WCF REST 入門套件之後,您會在 [Visual Studio 新增專案] 對話方塊中看到一組新的專案範本, (請參閱圖 12) 。 我已說明圖 13 中每個專案範本提供的內容。

您只需要選擇一個,輸入其餘的專案詳細資料,然後按 [確定]。 然後,您最後會得到一個基本架構 REST 專案,您可以立即執行並開始建置。 專案範本基本上會建置服務實作,就像上一節所示的服務實作一樣。

當您使用其中一個專案範本時,主要焦點在於修改資源類別定義,並在整個實作中傳播這些變更, (您可以使用 Visual Studio 重構來完成此) ,以及為每個 HTTP 作業實作 CRUD 方法存根。

讓我們逐步解說一些使用 WCF REST 入門套件專案範本的範例,以協助說明如何簡化建置這些 RESTful 服務類型的程式。

圖 12:WCF REST 入門套件專案範本

介面 描述

Atom 摘要 WCF 服務

產生範例 WCF 服務,示範如何以程式設計方式產生和傳回 SyndicationFeed。 您只需要變更實作,以填入您的商務資料摘要。

AtomPub WCF 服務

針對完全相容的 AtomPub 服務產生完整的基本架構實作,大幅減少您必須為此案例撰寫的程式碼數量,並讓您主要專注于如何將商務資源對應至 AtomPub 通訊協定。

HTTP 純 XML WCF 服務

產生不支援完整 HTTP 介面的 XML over HTTP 服務。 相反地,它提供簡單的 GET 和 POST 作業,並可讓您輕鬆地建置不是真正 RESTful 的 XML 型服務。

REST 集合 WCF 服務

產生服務,此服務會在資源集合周圍公開完整的 HTTP 介面 (GET、POST、PUT 和 DELETE) ,並提供基礎資源的 XML 和 JSON 標記法。

REST 單一 WCF 服務

產生服務,此服務會在單一資源周圍公開完整的 HTTP 介面 (GET、POST、PUT 和 DELETE) ,並提供基礎資源的 XML 和 JSON 標記法。

圖 13:WCF REST 入門套件專案範本

REST 單一服務

讓我們從建立簡單的服務開始,此服務會公開單一資源,代表我目前在任何時間點的所在位置。 我將建立新的「REST 單一 WCF 服務」專案,並將其命名為 MyWhereabouts。 產生專案之後,我會有一個 service.svc 檔案,其中包含我的服務實作, (程式碼實際上是在 service.svc.cs) 中找到。 此時,我可以實際按 F5 來測試說明初始產生的專案已完成且準備好執行的服務。

當我按下 F5 以載入服務並流覽至服務時,服務會以 XML 標記法傳回 < SampleItem > 資源,這會在瀏覽器中轉譯 (請參閱圖 14) 。

圖 14:流覽至產生的單一服務, (沒有變更)

如果您查看 service.svc.cs 內的原始程式碼,您會發現它們已提供 SampleItem 的類別定義 – 此類別代表服務實作所公開的單一資源。 其目的是要修改此類別定義,以代表您想要公開的實際資源。 此類別會在檔案的其他地方參考,因此您想要利用 Visual Studio 的重構支援,在進行變更之後傳播變更。 以下是初始類別的外觀:

// TODO: Modify the SampleItem. Use Visual Studio refactoring while modifying so
// that the references are updated.
/// <summary>
/// Sample type for the singleton resource.
/// By default all public properties are DataContract serializable
/// </summary>
public class SampleItem
{
    public string Value { get; set; }
}

而且,如果您查看服務類別定義,您會看到它衍生自 SingletonServiceBase < SampleItem > 和 ISingletonService < SampleItem > 。 現在您需要將 SampleItem 類別定義修改為更適當的專案。 由於我嘗試公開「my whereabouts」,所以會將類別名稱變更為 MyWhereabouts。 在進行時,我會使用重構功能表,然後選取 [將 'SampleItem' 重新命名為 'MyWhereabouts'] (請參閱圖 15) 。

圖 15:使用 Visual Studio 重構來變更資源類別名稱

選取此選項會負責在整個檔案的其餘部分重新命名類別參考,讓所有專案在變更之後仍應該建置並執行。

現在,我可以直接專注于修改類別定義,以模型化我嘗試公開的資源標記法。 針對 MyWhereabouts 資源,我只會新增一些稱為 Placename、Timezone、Lattitude 和 Longitude 的公用欄位。 以下是我的修改資源類別看起來的樣子:

public class MyWhereabouts
{
    public string Placename { get; set; }
    public string Timezone { get; set; }
    public string Lattitude { get; set; }
    public string Longitude { get; set; }
}

進行這項變更之後,我也修改了 MyWhere abouts 欄位初始化程式碼,以在撰寫此白皮書時將這些欄位設定為我的目前位置:

public class Service : SingletonServiceBase<MyWhereabouts>,
    ISingletonService<MyWhereabouts>
{
    // TODO: This variable used by the sample implementation. Remove if needed
    MyWhereabouts item = new MyWhereabouts()
    {
        Placename = "Fruit Heights",
        Timezone = "GMT-07:00 Mountain Time",
        Lattitude = "41.016962",
        Longitude = "-111.904238"
    };

有了這些變更之後,您現在可以再次按 F5 流覽至 service.svc 檔案,您應該會看到瀏覽器中顯示的 MyWhere abouts > 資源, (請參閱 < 圖 16) 。

圖 16:流覽至 MyWhereabouts 資源

此泛型服務實作支援完整的 HTTP 介面 (GET、POST、PUT 和 DELETE) ,而且每個作業都支援 XML 和 JSON 表示。 只要將 「?format=json」 新增至 URI 結尾,您就會以 JSON 格式擷取目前的資源。 您現在可以使用 Fiddler 或撰寫自訂用戶端,測試 POST、PUT 和 DELETE) 的其他 (作業。

REST 集合服務

接下來,讓我們建立 REST 集合服務來實作另一個 BookmarkService。 首先,我們將選取 [REST 集合 WCF 服務] 範本來建立新的專案。 如同上一個範例,我們最後會有一個 WCF 專案,其中包含名為 SampleItem 的資源類別,以及衍生自 CollectionServiceBase < SampleItem > 和 ICollectionService Bookmark > 的服務 < 類別。 這些基底類型會實作完整的 HTTP 介面,如先前所示。 現在,我們只需要進行一些變更。

我們需要變更的第一件事是資源類別的名稱– 我們將它從 「SampleItem」 變更為 「Bookmark」,而我將會利用 Visual Studio 重構來再次傳播整個專案的變更。 現在我可以填入 Bookmark 類別,其中包含代表書簽資源所需的欄位。 我會將 Bookmark 類別定義變更為下列專案:

public class Bookmark
{
    public Uri Url { get; set; }
    public string User { get; set; }
    public string Title { get; set; }
    public string Tags { get; set; }
    public bool Public { get; set; }
    public DateTime LastModified { get; set; }
}

有了這些變更,我的新書簽收集服務便已準備好進行測試。 只要在 Visual Studio 中按 F5,它會載入服務並流覽至 service.svc 檔案。 當瀏覽器出現時,您會看到空 < 的 ItemInfoList > 元素,因為 Bookmark 集合目前是空的。

此時,您可以使用 Fiddler 之類的專案,透過 POST) 將某些書簽新增至集合 (,或者您可以在服務建構函式中預先填入 Bookmark 專案集合。 填入 Bookmark 集合之後,當您再次流覽至服務時,將會取得一些 < Bookmark > 元素, (請參閱圖 17) 。 請注意,產生的清單包含個別書簽資源的連結。

您可以依照其中一 < 個 ItemInfo > 元素中找到的 < EditLink > 元素來流覽至個別書簽。 例如,您可以流覽至 「 https://localhost:26826/Service.svc/7b5b4a15-3b05-4f94-a7b8-1f324b5cfc7d 」 來取得集合中的第一個書簽, (請參閱圖 18) 。

圖 17:流覽至書簽收集服務

圖 18:流覽至集合內的單一書簽

此時,您也可以使用 POST、PUT 和 DELETE 作業,而不需要任何額外的程式碼,產生的專案範本已經包含每個專案的預設實作。 您可以將新的 < Bookmark 元素張貼至服務的根位址,它會產生新的 Bookmark > 資源,並指派新的識別碼,並傳回具有對應位置標頭的 201 Created 回應。 您也可以將 Bookmark > 元素放入 < 個別書簽 URI,以執行更新。 而且您可以將 DELETE 要求傳送至個別書簽資源,以從集合中移除它們。

如您所見,針對這種特定類型的 RESTful 服務, (集合導向的服務) ,WCF REST 入門套件可讓您以非常少的程式碼在我們的部分上啟動並執行實作。

Atom 摘要服務

當您需要產生簡單的 Atom 摘要服務時,請從 WCF REST 入門套件建立類型為 「Atom Feed WCF Service」 的新專案。 它會產生 WCF 服務,其中包含範例摘要實作,例如圖 19 所示的範例摘要實作。 此服務會依原狀執行,並產生可在任何標準 Atom 讀取器中轉譯的範例 Atom 摘要 (請參閱圖 20,以查看它在 IE) 中的轉譯方式。

// TODO: Please set IncludeExceptionDetailInFaults to false in production
// environments
[ServiceBehavior(IncludeExceptionDetailInFaults = true),
 AspNetCompatibilityRequirements(RequirementsMode =
     AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class FeedService
{
    // TODO: Modify the URI template and method parameters according to your
    // application. An example URL is http://<url-for-svc-file>?numItems=1
    [WebHelp(Comment = "Sample description for GetFeed.")]
    [WebGet(UriTemplate = "?numItems={i}")]
    [OperationContract]
    public Atom10FeedFormatter GetFeed(int i)
    {
        SyndicationFeed feed;
        // TODO: Change the sample content feed creation logic here
        if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
            "numItems cannot be negative", null);
        if (i == 0) i = 1;
        // Create the list of syndication items. These correspond to Atom entries
        List<SyndicationItem> items = new List<SyndicationItem>();
        for (int j = 1; j <= i; ++j)
        {
            items.Add(new SyndicationItem()
            {
                // Every entry must have a stable unique URI id
                Id = String.Format(CultureInfo.InvariantCulture,
                    "http://tempuri.org/Id{0}", j),
                Title = new TextSyndicationContent(
                    String.Format("Sample item '{0}'", j)),
                // Every entry should include the last time it was updated
                LastUpdatedTime = new DateTime(
                    2008, 7, 1, 0, 0, 0, DateTimeKind.Utc),
                // The Atom spec requires an author for every entry. If the entry has
                // no author, use the empty string
                Authors =
                {
                    new SyndicationPerson()
                    {
                        Name = "Sample Author"
                    }
                },
                // The content of an Atom entry can be text, xml, a link or arbitrary
                // content. In this sample text content is used.
                Content = new TextSyndicationContent("Sample content"),
            });
        }
        // create the feed containing the syndication items.
        feed = new SyndicationFeed()
        {
            // The feed must have a unique stable URI id
            Id = "http://tempuri.org/FeedId",
            Title = new TextSyndicationContent("Sample feed"),
            Items = items
        };
        feed.AddSelfLink(
            WebOperationContext.Current.IncomingRequest.GetRequestUri());
        #region Sets response content-type for Atom feeds
        WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
        #endregion
        return feed.GetAtom10Formatter();
    }
}

圖 20:Internet Explorer 中轉譯的範例摘要

此範本只會提供用來產生一般 Atom 摘要服務的範例程式碼。 您必須修改程式碼,以將商務實體對應至 SyndicationFeed 實例,並將每個個別實體對應至新的 SyndicationItem 實例,以利用它所提供的欄位。

AtomPub 服務

當您想要實作符合 Atom 發行通訊協定的服務時,您應該使用 WCF REST 入門套件隨附的「Atom 發行通訊協定 WCF 服務」專案範本。 此範本會產生可公開單一範例集合的完整 AtomPub 服務。 產生的服務類別衍生自先前所述的 AtomPubServiceBase 和 IAtomPubService。

您可以流覽至 service.svc 檔案立即測試服務,而服務會傳回 AtomPub 服務檔,描述其支援的集合 (請參閱圖 21) 。 如您所見,此服務會公開名為「範例集合」的集合,您可以藉由將 「collection1」 新增至服務根 URL 的結尾來存取。 當您存取集合時,服務會傳回代表範例集合的 Atom 摘要, (請參閱圖 22) 。 此服務也支援透過標準 AtomPub HTTP 介面新增、更新和刪除 Atom 專案。

當您使用 WCF REST Starter Kit 建置 AtomPub 服務時,您的作業是專注于您想要公開的邏輯集合。 您必須定義服務所公開的商務實體集合與 AtomPub 集合之間的對應,這基本上是定義自訂商務實體類別與 WCF SyndicationFeed/Item 類別之間的對應。

圖 21:流覽至 AtomPub 服務

圖 22:流覽至 AtomPub 服務公開的範例集合

HTTP 純文字 XML 服務

如果您真的不需要或想要執行完全 RESTful 服務, (遵守所有 REST 條件約束並支援完整的 HTTP 介面) ,但您會想要使用簡單的 XML-over-HTTP 服務來結算,您不想使用我們剛才涵蓋的專案範本。 相反地,您會想要查看「HTTP 純文字 XML WCF 服務」專案範本,而不會嘗試提供 RESTful 實作。 相反地,它會提供一些範例 XML-over-HTTP 作業,以協助您開始使用。

圖 23 顯示當您使用此專案範本時將取得的範例實作。 請注意它如何提供一個 [WebGet] 作業來接受一些查詢字串參數做為輸入,並提供另一個接受並傳回 XML 實體主體的 [WebInvoke] 作業。  這些作業只是提供作為範例,示範如何開始使用 (POX) 的一般舊 XML 服務。

如果您嘗試建置傳回資料的 POX 服務,您可以保留 GetData 方法,並調整要求/回應資料以符合您的需求。 如果您需要支援 POST 要求,您可以保留 DoWork 方法並據以調整。 您最後會完全重寫這些方法,因此這個特定專案範本不會提供其他方法的價值。

[ServiceBehavior(IncludeExceptionDetailInFaults = true),
 AspNetCompatibilityRequirements(RequirementsMode =
     AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class Service
{
    [WebHelp(Comment = "Sample description for GetData")]
    [WebGet(UriTemplate = "GetData?param1={i}&param2={s}")]
    [OperationContract]
    public SampleResponseBody GetData(int i, string s)
    {
        // TODO: Change the sample implementation here
        if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
            "param1 cannot be negative", null);
        return new SampleResponseBody()
        {
            Value = String.Format("Sample GetData response: '{0}', '{1}'", i, s)
        };
    }
    [WebHelp(Comment = "Sample description for DoWork")]
    [WebInvoke(UriTemplate = "DoWork")]
    [OperationContract]
    public SampleResponseBody DoWork(SampleRequestBody request)
    {
        //TODO: Change the sample implementation here
        return new SampleResponseBody()
        {
            Value = String.Format("Sample DoWork response: '{0}'", request.Data)
        };
    }
}

圖 23:HTTP 純 XML WCF 服務範例實作

使用 HttpClient 取用 RESTful 服務

使用 RESTful 服務的其中一個更具挑戰性層面是撰寫用戶端程式代碼來取用它們。 由於 RESTful 服務不提供類似 WSDL 的中繼資料,因此用戶端開發人員不會享有程式碼產生和強型別 Proxy 類別的便利性,因此大部分 SOAP 服務都很容易與程式設計方式整合。 這種事實通常會導致開發人員認為與 RESTful 服務整合比一般 SOAP 服務更麻煩。 在我的意見中,這主要是觀點。 其金鑰是選擇正確的用戶端 HTTP API 來針對它進行程式設計。

由於 REST 服務只是以 HTTP 為基礎的服務,因此您可以常使用任何 HTTP API 來取用它們。 這可讓您在用戶端上有極大的彈性。 Microsoft .NET 提供程式設計 HTTP 用戶端程式代碼的 System.Net 類別,例如 WebRequest 和 WebResponse。 這些類別會完成工作,但是它們會讓用戶端體驗變得比應該更複雜。  用來使用 SOAP 型 Proxy 的開發人員通常找不到非常吸引人或自然的 Proxy。

為了簡化取用 RESTful 服務的用戶端程式設計體驗,WCF REST Starter Kit (Preview 2) 隨附名為 HttpClient 的新 HTTP API,可提供更自然的模型來設計統一 HTTP 介面,以及許多擴充方法,讓您更輕鬆地處理 HTTP 訊息中找到的各種不同內容類型。

使用 HttpClient 消費者入門

HttpClient 類別提供簡單的 API 來傳送 HTTP 要求和處理 HTTP 回應。 此功能定義在兩個主要類別定義中–一個稱為 HttpClient,另一個稱為 HttpMethodExtensions (請參閱圖 24) 。 前者會定義 類別的基本功能,而後者則提供許多以不同邏輯 HTTP 方法為目標的分層擴充方法。

如果您檢查 HttpClient 類別,您會看到它提供特定目標基底位址的方法、操作 HTTP 要求標頭的方式,以及許多多載來傳送要求。 您可以透過 HttpContent 實例提供要求訊息內容,並透過您傳回的 HttpResponseMessage 物件處理回應。 類別也會針對您不想在等候回應返回時封鎖呼叫執行緒的情況,提供非同步 Send 方法。

HttpMethodExtensions 類別可讓您更輕鬆地將數個擴充方法新增至 HttpClient,以發出邏輯 Get、Post、Put 和 Delete 要求。 這些是使用 HttpClient 取用 RESTful 服務時最有可能使用的方法。

public class HttpClient : IDisposable
{
    public HttpClient();
    public HttpClient(string baseAddress);
    public HttpClient(Uri baseAddress);
    public Uri BaseAddress { get; set; }
    public RequestHeaders DefaultHeaders { get; set; }
    public IList<HttpStage> Stages { get; set; }
    public HttpWebRequestTransportSettings TransportSettings { get; set; }
    public event EventHandler<SendCompletedEventArgs> SendCompleted;
    public IAsyncResult BeginSend(HttpRequestMessage request,
        AsyncCallback callback, object state);
    protected virtual HttpStage CreateTransportStage();
    public void Dispose();
    protected virtual void Dispose(bool disposing);
    public HttpResponseMessage EndSend(IAsyncResult result);
    public HttpResponseMessage Send(HttpMethod method);
    public HttpResponseMessage Send(HttpRequestMessage request);
    public HttpResponseMessage Send(HttpMethod method, string uri);
    public HttpResponseMessage Send(HttpMethod method, Uri uri);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        RequestHeaders headers);
    public HttpResponseMessage Send(HttpMethod method, Uri uri, HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, Uri uri,
        RequestHeaders headers);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        RequestHeaders headers, HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, Uri uri,
        RequestHeaders headers, HttpContent content);
    public void SendAsync(HttpRequestMessage request);
    public void SendAsync(HttpRequestMessage request, object userState);
    public void SendAsyncCancel(object userState);
    protected void ThrowIfDisposed();
}
public static class HttpMethodExtensions
{
    public static HttpResponseMessage Delete(this HttpClient client, string uri);
    public static HttpResponseMessage Delete(this HttpClient client, Uri uri);
    public static HttpResponseMessage Get(this HttpClient client);
    public static HttpResponseMessage Get(this HttpClient client, string uri);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri,
        HttpQueryString queryString);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri,
        IEnumerable<KeyValuePair<string, string>> queryString);
    public static HttpResponseMessage Head(this HttpClient client, string uri);
    public static HttpResponseMessage Head(this HttpClient client, Uri uri);
    public static HttpResponseMessage Post(this HttpClient client, string uri,
        HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, Uri uri,
        HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, string uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, Uri uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, string uri,
        HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, Uri uri,
        HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, string uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, Uri uri,
        string contentType, HttpContent body);
}

圖 24:HttpClient 和 HttpMethodExtensions 類別定義

讓我們看看一個範例,以瞭解 HttpClient 如何簡化專案。 我們將取用網路上未使用 .NET 建置的實際 RESTful 服務。 我們將使用 Twitter REST API。

如果您流覽至 的 http://apiwiki.twitter.com/Twitter-API-Documentation Twitter REST API 檔,您可以快速瞭解如何開始發出適當的 HTTP 要求以與服務整合。 下列程式碼示範如何擷取 Twitter 使用者的「朋友時間軸」:

HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
    new NetworkCredential("{username}", "{password}");
HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatuses(resp.Content.ReadAsStream());

當我們建構 HttpClient 實例時,我們會提供 Twitter REST API 服務的基底位址,然後透過 TransportSettings.Credentials 屬性提供使用者的 HTTP 認證。  現在,我們已準備好使用各種 Get、Post、Put 和 Delete 方法來與服務公開的不同資源互動。 在此範例中,我呼叫 Get 從服務擷取「friends_timeline.xml」資源。 一旦傳回,我就可以在 resp 物件上呼叫 EnsureStatusIsSuccessful,以確保我們回到 200 層級的 HTTP 狀態碼。

接下來,我們需要處理回應訊息的內容。 其中一種方法是將回應讀出為數據流,然後使用您最愛的 XML API 處理串流 (ReadAsStream) 。 以下是示範如何使用 XmlDocument 處理回應 XML 的範例:

static void ProcessStatuses(Stream str)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(str);
    XmlNodeList statuses = doc.SelectNodes("/statuses/status");
    foreach (XmlNode n in statuses)
        Console.WriteLine("{0}: {1}",
            n.SelectSingleNode("user/screen_name").InnerText,
            n.SelectSingleNode("text").InnerText);
}

HttpClient 類別隨附將訊息內容當做資料流程、字串或位元組陣列處理的功能。 除了這個基本功能之外,WCF REST Start Kit (Preview 2) 隨附另一個稱為 Microsoft.Http.Extensions 的元件,其中包含許多擴充方法,可讓您使用其他熱門技術來處理訊息內容, (XLinq、XmlSerializer、SyndicationFeed 等) 。 我們將在下一節中看到這些擴充方法的運作方式。

讓我們看看另一個範例,示範如何更新使用者的 Twitter 狀態。 您可以使用下列程式碼來完成此作業, (假設 HttpClient 已在上述) 具現化:

HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", "my first HttpClient app");
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();

「update.xml」 資源需要 POST 要求,而且預期訊息包含包含新狀態文字的 URL 編碼字串。 HttpClient 可讓您輕鬆地透過 HttpUrlEncodedForm 類別產生 URL 編碼的訊息,然後直接呼叫 Post 提供內容。

這個快速範例說明如何使用 HttpClient 發出 GET 和 POST 要求。 在下列各節中,我們將深入探討 HttpClient 詳細資料,並醒目提示其中一些主要功能。

處理訊息內容

一般而言,您會想要使用比 Stream 更複雜一些的 HTTP 回應主體來處理 HTTP 回應的主體。 WCF REST Starter Kit (Preview 2) 隨附另一個稱為 Microsoft.Http.Extensions 的元件,可透過許多擴充方法增強 HttpContent 類別,讓您可以使用特定 API 來處理訊息內容。

在目前版本中,這些擴充方法散佈在數個命名空間中。 請務必注意,除非您已將參考新增至 Microsoft.Http.Extensions.dll,且已將 using 語句新增至適當的命名空間,否則不會在 intellisense 中看到它們。 圖 25 列出這個額外元件所提供的 HttpContent 擴充方法。

擴充方法 命名空間

ReadAsDataContract

System.Runtime.Serialization

ReadAsJsonDataContract

System.Runtime.Serialization.Json

ReadAsServiceDocument

System.ServiceModel.Syndication

ReadAsSyndicationFeed

System.ServiceModel.Syndication

ReadAsXmlReader

System.Xml

ReadAsXElement

System.Xml.Linq

ReadAsXmlSerializable

System.Xml.Serialization

圖 25:HttpContent 擴充方法

讓我們看看一些範例,示範如何使用其中一些擴充方法。 我們將從 XLinq 範例開始,因為它是現今常見的 .NET XML 程式設計選擇。 我們現在會呼叫 ReadAsXElement,而不是在 Content 屬性上呼叫 ReadAsStream,如下所示:

HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsXElement(resp.Content.ReadAsXElement());

請記住,您將需要 Microsoft.Http.Extensions 元件的參考,而且您必須將 using 語句新增至檔案以進行System.Xml。Linq – 假設您已完成這兩個步驟,您應該會在 Content 屬性的 Intellisense 中看到 ReadAsXElement。 下列範例說明如何以 XElement 處理訊息內容:

static void ProcessStatusesAsXElement(XElement root)
{
    var statuses = root.Descendants("status");
    foreach (XElement status in statuses)
        Console.WriteLine("{0}: {1}",
            status.Element("user").Element("screen_name").Value,
            status.Element("text"));
}

如果您想要使用 XmlReader 而非 XElement 來處理訊息內容,則非常類似–您只需呼叫 ReadAsXmlReader 並據以處理內容即可。

許多開發人員偏好利用 XML 序列化技術,而不是直接使用 XML API,這可讓他們使用強型別 .NET 類型,而不是泛型 XML 節點。 ReadAsDataContract 和 ReadAsJsonDataContract 擴充方法可讓您針對 XML 或 JSON 內容分別利用 DataContractSerializer () 進行序列化,而 ReadAsXmlSerializable 可讓您利用 XmlSerializer 序列化引擎。

不過,在您可以使用任何以序列化為基礎的方法之前,您必須取得一些 .NET 類別,以適當地建立要處理的訊息內容模型。 您可以從服務所提供的 XML 架構定義,或從您在開發期間實際使用服務時擷取的一些範例 XML 產生適當的類別。 但是,如果所有其他專案都失敗,您一律可以根據您對 XML 格式的知識手動撰寫這些類型。

例如,以下是一些 C# 類別,這些類別會在使用 DataContractSerializer 來執行還原序列化時,將我們從上述 Twitter 取得的狀態模型化:

[assembly: ContractNamespace("", ClrNamespace = "TwitterShell")]
[CollectionDataContract(Name = "statuses", ItemName = "status")]
public class statusList : List<status> { }
public class user
{
    public string id;
    public string name;
    public string screen_name;
}
public class status
{
    public string id;
    public string text;
    public user user;
}

有了這些類別,我們現在可以使用 ReadAsDataContract 方法,指定還原序列化 (程式期間要使用的根類型,在此案例中為 statusList) :

HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsDataContract<statusList>());

既然我們已將訊息內容還原序列化為 statusList 物件,處理常式代碼會變得更簡單,如這裡所示:

static void ProcessStatusesAsDataContract(statusList list)
{
    foreach (status status in list)
        Console.WriteLine("{0}: {1}", status.user.screen_name, status.text);
}

如果 HTTP 回應會以 JSON 格式傳回 (,而不是 XML) ,您可以直接呼叫 ReadAsJsonDataContract,如下所示:

HttpResponseMessage resp = http.Get("friends_timeline.json");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsJsonDataContract<statusList>());

其餘的處理常式代碼會維持不變。

此外,如果您想要使用 XmlSerializer 引擎,您必須先確定您有與 XmlSerializer-mapping 相容的可序列化類型。 在此特定範例中,我們需要將 statusList 類型取代為下列根類型 (,因為 XML 對應不同) :

public class statuses
{
    [XmlElement("status")]
    public status[] status;
}

然後,您只需呼叫 ReadAsXmlSerializable 而不是 ReadAsDataContract,即可指定要還原序列化之根類型的狀態。  一般而言,XmlSerializer 比 DataContractSerializer 更有彈性,就能夠處理的 XML 而言。 因此,XmlSerializer 可能會成為使用 RESTful 服務的較常見選擇。 此外,WCF REST Starter Kit (Preview 2) 隨附 Visual Studio 外掛程式,可讓您輕鬆地產生 XmlSerializer 類型。 我們將在下一節中討論其運作方式。

最後一個範例是,如果服務傳回 Atom 摘要,該怎麼辦? 在此情況下,您可以直接呼叫 ReadAsSyndicationFeed,然後取得 SyndicationFeed 物件來處理:

HttpResponseMessage resp = http.Get("friends_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());

然後,您可以處理 SyndicationFeed 物件來擷取感興趣的資訊, (您必須知道資訊位於 Atom 摘要結構內的位置) :

static void ProcessStatusesAsFeed(SyndicationFeed feed)
{
    foreach (SyndicationItem item in feed.Items)
        Console.WriteLine("{0}: {1}", item.Authors[0].Name, item.Title.Text);
}

如您所見,新的 HttpClient 類別可讓您輕鬆地在 .NET 中使用 RESTful 服務。 在 Microsoft.Http.Extensions 中找到的新擴充方法提供訊息處理的大量彈性,並簡化一些最常見的 REST 案例, (XML、JSON 和 Atom 等) 。

Visual Studio 中的「貼上 XML 為類型」

由於 RESTful 服務未隨附于 WSDL,因此無法產生強型別 Proxy,就像您用於 SOAP 一樣。 不過,由於所有 REST 服務都會 (HTTP 統一介面) 實作相同的服務合約,因此除了 HttpClient (Get、Post、Put 和 Delete) 所提供的作業之外,您不需要任何專案。 不過,您可能想要一些可序列化的型別來簡化 HTTP 訊息處理邏輯,我已示範如何在上一節中執行。

如果有問題的服務提供 XML 架構定義來描述自動「說明」頁面之類的內容 (,則會在服務端) 提供 WCF REST 入門套件,您可以使用xsd.exe或svcutil.exe之類的工具來產生適當的類型。 如果沒有,您可以利用 WCF REST Starter Kit (Preview 2) 稱為「貼上 XML 為類型」的新 Visual Studio 外掛程式。

這項新功能可讓您將 XML 架構定義或範例 XML 實例複製到剪貼簿中,然後您可以從 [編輯] 功能表選取 [貼上 XML 為類型]。 當您這樣做時,它會為剪貼簿中的架構/XML 產生適當的 XmlSerializer 類型,並將其貼到檔案中的目前位置。 使用 XML 範例實例時,它不一定能夠產生完全精確的類型,因此您可能需要在產生之後進行一些額外的大規模調整。

讓我們看看如何搭配 Twitter 範例來使用此功能。 如果您使用瀏覽器 () http://twitter.com/statuses/friends\_timeline.xml 流覽至 Twitter「friends_timeline.xml」資源,您將取得此資源傳回的實際 XML (請參閱圖 26) 。 現在請執行「檢視來源」,並將 XML 複製到剪貼簿。 複製之後,您可以返回 Visual Studio,並將游標放在我們想要產生的類型移至何處。 然後,只要從 [編輯] 功能表選取 [貼上 XML 為類型], (請參閱圖 27) ,並將必要的 XmlSerializer 類型新增至檔案。 就地之後,您可以搭配 ReadAsXmlSerializable 使用這些類型來處理訊息內容。

您可以在實作服務時,搭配 WCF REST 入門套件提供的 [說明] 頁面使用此功能。 您可以在開發期間流覽說明頁面、流覽至不同資源和作業的架構或範例 XML 訊息,而且您可以使用此外掛程式在用戶端應用程式內產生適當的訊息類型。 一般而言,這會降低嘗試與 RESTful 服務整合的用戶端,並簡化開發人員體驗的列。

圖 26:Twitter friends_timeline.xml 資源傳回的範例 XML

圖 27:將 XML 貼上為 [類型] 功能表項目

處理服務輸入

上一節著重于處理 HTTP 回應訊息中找到的訊息內容。 也請務必考慮如何產生 RESTful 服務預期的適當輸入。 許多服務都接受以查詢字串、URL 編碼表單資料的形式,或透過各種可在 HTTP 要求實體主體內使用的其他格式來接受輸入 (,例如 XML、JSON、Atom 等) 。

如果服務需要查詢字串輸入,您可以使用 HttpQueryString 類別正確地建置它。 下列範例示範如何建置我們可以提供給 Get 方法的查詢字串:

HttpQueryString vars = new HttpQueryString();
vars.Add("id", screenname);
vars.Add("count", count);
resp = http.Get(new Uri("user_timeline.xml", UriKind.Relative), vars);
resp.EnsureStatusIsSuccessful();
DisplayTwitterStatuses(resp.Content.ReadAsXElement());

如果服務需要 URL 編碼的表單輸入 (例如,從 HTML < 表單 > 提交) 取得的內容,您可以使用 HttpUrlEncodedForm 或 HttpMultipartMimeForm 類別來建置適當的內容, (稍後如果您需要產生多部分 MIME 表單) , 下列範例說明如何建置我們可以提供給 Post 方法的簡單表單提交:

HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", status);
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();
Console.WriteLine("Status updated!");

除了這些類別之外,Microsoft.Http.Extensions 元件還隨附一些額外的擴充方法,可簡化產生 HttpContent 物件的程式,您可以提供給 HttpClient 做為輸入。 這些方法定義在 HttpContentExtensions 類別中,並包含從 XElement () 、CreateDataContract、CreateJsonDataContract、CreateXmlSerializable、CreateAtom10SyndicationFeed 和 CreateRss20SyndicationFeed 等專案。 當您需要為 HTTP 要求訊息產生下列其中一種內容類型時,您會想要使用這些方法。

使用具型別標頭簡化標頭處理

Microsoft.Http 元件也隨附一套強型別 HTTP 標頭類別。 您會在 Microsoft.Http.Headers 命名空間中找到它們。 例如,您會發現 CacheControl、Connection、Cookie、Credential、EntityTag、Expect 等類別以及其他類別。

HttpClient 類別提供 DefaultHeaders 屬性,可透過這些標頭類型向您公開要求標頭。 您可以在傳送要求之前操作個別標頭。 HttpResponseMessage 類別也隨附 Headers 屬性,可透過這些標頭類別再次向您公開各種 HTTP 回應標頭。  下列範例說明如何操作要求/回應中的標頭,以發出條件式 GET 要求:

HttpResponseMessage resp = http.Get("public_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());
DateTime? lastAccessDate = resp.Headers.Date;
...
http.DefaultHeaders.IfModifiedSince = lastAccessDate;
resp = http.Get("public_timeline.atom");
Console.WriteLine("status={0}", resp.StatusCode);

這些強型別的 HTTP 標頭類別可讓您更輕鬆地在程式碼中使用 HTTP 標頭,因為它們會保護您免于基礎 HTTP 通訊協定詳細資料的許多層面。

HttpClient 「階段」處理

HttpClient 類別隨附可插入自訂程式碼的要求攔截機制,類似于服務端 WebServiceHost2 所提供的 RequestInterceptor 模型。 此要求攔截模型專為用戶端 HTTP 互動所設計。

以下是其運作方式。 HttpClient 類別會管理由 HttpStage 類別建立模型的「HTTP 處理階段」集合。 傳送任何訊息之前,您可以使用 HttpClient 實例來設定 HttpStage 衍生物件的集合。 然後,每當發出 HTTP 要求時,HttpClient 實例會呼叫每個 HttpStage 物件,讓您有機會執行其處理。

有幾種類別衍生自 HttpStage、HttpProcessingStage 和 HttpAsyncStage,分別提供同步和非同步模型。 實作您自己的自訂階段時,您通常會衍生自這兩個類別的其中一個。 當您從這些類別衍生時,會是您的工作來覆寫 ProcessRequest 和 ProcessResponse 方法來定義邏輯。

下列程式碼說明如何實作自訂 HttpProcessingStage,該自訂 HttpProcessingStage 只會將訊息列印到主控台視窗:

public class MyHttpStage : HttpProcessingStage
{
    public override void ProcessRequest(HttpRequestMessage request)
    {
        Console.WriteLine("ProcessRequest called: {0} {1}",
            request.Method, request.Uri);
    }
    public override void ProcessResponse(HttpResponseMessage response)
    {
        Console.WriteLine("ProcessResponse called: {0}",
            response.StatusCode);
    }
}

實作自訂 HttpStage 衍生類別之後,就可以在使用 HttpClient 時利用它。 下列範例說明如何在發出任何 HTTP 要求之前,將 MyHttpStage 實例新增至混合:

HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
    new NetworkCredential("skonnarddemo", "baby95");
// configure the custom stage
http.Stages.Add(new MyHttpStage());
HttpResponseMessage resp = http.Get("public_timeline.atom");

現在,當您呼叫 Get 方法時,您會看到在發出 HTTP 要求給目標服務之前和之後,列印到主控台視窗的 MyHttpStage 訊息。 您可以使用這項攔截技術來實作各種用戶端 HTTP 處理需求, (例如安全性、記錄、追蹤、自訂快取等) ,以提升跨 HTTP 用戶端應用程式的重複使用性。

擴充 HttpClient 以建立特製化用戶端

除了 HttpStage 擴充性機制之外,也可以衍生自 HttpClient 來建立您自己的特製化 REST 用戶端程式庫。 這可讓您為 RESTful 服務的使用者提供更有意義的方法和屬性,而且最終可以提供開發人員體驗,讓平行處理 (不會超過一般 SOAP Proxy 類別體驗) 。

作為 WCF REST Starter Kit (Preview 2) 的一部分,它們提供稱為 AtomPubClient 的特製化 HttpClient 衍生類別範例。 它提供與標準 AtomPub 服務互動的自訂用戶端體驗。 以下是 AtomPubClient 類別定義的外觀:

public class AtomPubClient : HttpClient
{
    public AtomPubClient();
    public SyndicationItem AddEntry(SyndicationFeed feed, SyndicationItem newEntry);
    public SyndicationItem AddEntry(Uri feedUri, SyndicationItem newEntry);
    public SyndicationItem AddMediaResource(SyndicationFeed feed, string contentType,
        string description, HttpContent mediaContent);
    public SyndicationItem AddMediaResource(Uri mediaCollectionUri,
        string contentType, string description, HttpContent mediaContent);
    public void DeleteEntry(SyndicationItem entry);
    public void DeleteEntry(Uri itemUri);
    public SyndicationItem GetEntry(Uri itemUri);
    public SyndicationFeed GetFeed(Uri feedUri);
    public ServiceDocument GetServiceDocument(Uri serviceDocumentUri);
    public SyndicationItem UpdateEntry(SyndicationItem oldValue,
        SyndicationItem newValue);
    public SyndicationItem UpdateEntry(Uri editUri, SyndicationItem newValue);
}

請注意它如何提供 AddEntry、GetEntry、UpdateEntry、GetFeed 等 AtomPub 服務特有的方法,這是更自然的方式來思考與這些類型的服務互動,而不是在 HttpClient 上使用基礎 Get、Post、Put 和 Delete 方法。

下列程式碼範例說明如何使用 AtomPubClient 類別來巡覽 AtomPub 服務,並將新的 Atom 專案新增至第一個工作區集合:

AtomPubClient client = new AtomPubClient();
ServiceDocument doc = client.GetServiceDocument(
    new Uri("https://localhost:30807/Service.svc/"));
Uri feedUri = doc.Workspaces[0].Collections[0].Link;
SyndicationFeed feed = client.GetFeed(feedUri);
SyndicationItem item = new SyndicationItem()
{
    Title = new TextSyndicationContent("New Item"),
    PublishDate = DateTime.Now
};
client.AddEntry(feed, item);

WCF REST Starter Kit (Preview 2) 也隨附名為 PollingAgent 的類別,可讓您更輕鬆地實作「輪詢」服務資源的用戶端邏輯,並且只在資源變更時執行動作。 您可以使用 PollingAgent 搭配實際執行 HTTP 工作的 HttpClient 物件。 以下是 PollingAgent 的完整類別定義:

public class PollingAgent : IDisposable
{
    public PollingAgent();
    public HttpClient HttpClient { get; set; }
    public bool IgnoreExpiresHeader { get; set; }
    public bool IgnoreNonOKStatusCodes { get; set; }
    public bool IgnoreSendErrors { get; set; }
    public TimeSpan PollingInterval { get; set; }
    public event EventHandler<ConditionalGetEventArgs> ResourceChanged;
    public void Dispose();
    public void StartPolling();
    public void StartPolling(Uri uri);
    public void StartPolling(Uri uri, EntityTag etag, DateTime? lastModifiedTime);
    public void StopPolling();
}

因此,您會建立 PollingAgent 類別的實例,並提供要使用的 HttpClient 物件。 然後,您可以建立 ResourceChanged 事件的回呼方法,並指定輪詢間隔。 一旦準備就緒,您只要呼叫 StartPolling 並提供目標資源 URI 即可。 下列程式碼範例說明如何針對 Twitter 公用時程表進行「輪詢」設定:

class Program
{
    static void Main(string[] args)
    {
        PollingAgent pollingClient = new PollingAgent();
        pollingClient.HttpClient = new HttpClient();
        pollingClient.HttpClient.TransportSettings.Credentials =
            new NetworkCredential("skonnarddemo", "baby95");
        pollingClient.PollingInterval = TimeSpan.FromSeconds(10);
        pollingClient.ResourceChanged += new EventHandler<ConditionalGetEventArgs>(
            pollingClient_ResourceChanged);
        pollingClient.StartPolling(
            new Uri("http://twitter.com/statuses/public_timeline.xml"));
        Console.WriteLine("polling...");
        Console.ReadLine();
    }
    static void pollingClient_ResourceChanged(object s, ConditionalGetEventArgs e)
    {
        ProcessStatusesAsXElement(e.Response.Content.ReadAsXElement());
    }
    static void ProcessStatusesAsXElement(XElement root)
    {
        var statuses = root.Descendants("status");
        foreach (XElement status in statuses)
            Console.WriteLine("{0}: {1}",
                status.Element("user").Element("screen_name").Value,
                status.Element("text"));
    }
}

如您所見,HttpClient 提供簡單的 HTTP 基礎,可讓您擴充以提供更特製化的用戶端程式設計模型。 投資一些時間來建置您自己的 HttpClient 衍生類別,在用戶端開發人員體驗方面支付大量收益。

結論

Microsoft 正努力提供一個第一級的程式設計模型,以使用 Microsoft .NET 架構來實作及取用 RESTful 服務。 WCF 3.5 引進建置 RESTful 服務所需的基本「Web」程式設計模型,但它只是一個啟動。 WCF REST 入門套件是 Microsoft 贊助的 CodePlex 專案,提供一組專為簡化 REST 焦點開發工作而設計的 WCF 延伸模組和重要 Visual Studio 整合。 目前在 WCF REST 入門套件中找到的許多功能,都很可能發現其進入 .NET 架構的未來版本,但沒有理由等待,您現在可以開始將這些功能放在今天使用。

關於Author

Aaron Skonnard 是 Pluralsight 的共同建立者,Microsoft 訓練提供者提供講師導向和隨選開發人員課程。 這幾天,Aaron 會花大部分時間錄製 Pluralsight 隨選! 著重于雲端運算、Windows Azure、WCF 和 REST 的課程。 Aaron 花費了數年的時間撰寫、說話及教導世界各地的專業開發人員。 您可以在 和 http://twitter.com/skonnardhttp://pluralsight.com/aaron 絡他。

其他資源