共用方式為


本文章是由機器翻譯。

OData 和 AtomPub

使用 WCF 資料服務綁定 AtomPub 伺服器

Chris Sells

下載代碼示例

如果您不熟悉開放資料協定 (OData),我要告訴您它很美妙。OData(在 odata.org 上有詳細介紹)以下列各種基於 HTTP 的功能優勢為基礎:用於發佈資料的 Atom;用於創建、更新和刪除資料的 AtomPub;以及用於定義資料類型的 Microsoft 實體資料模型 (EDM)。

如果您擁有 JavaScript 用戶端,則可以採用 JSON 格式(而不是 Atom 格式)直接返回資料;如果您擁有其他用戶端(包括 Excel、.Microsoft NET Framework、PHP、AJAX 等),則可以使用用戶端庫來形成 OData 請求和處理 OData 回應。如果您在伺服器端使用 .NET Framework,則 Microsoft 還提供一個易於使用的庫,該庫稱為 WCF 資料服務,用於公開 Microsoft 實體框架支援的 .NET Framework 類型或資料庫作為 OData 源。這樣,您就可以採用基於 HTTP 的方式和標準方式,通過 Internet 輕鬆公開您的資料。

話雖如此,您也可能希望使用 OData 執行一些並不完全屬於現成功能的任務,如將 OData 與現有基於 Atom 和 AtomPub 的閱讀器和編寫器集成。這正是我們要嘗試的功能。

一個簡單的博客

例如,假設我要構建一個簡單的博客系統(事實上,此工作基於我對 sellsbrothers.com 上的內容管理系統的重新編寫)。我對 Visual Studio 2010 中的模型優先支援十分著迷,因此創建了一個 ASP.NET MVC 2.0 專案,添加了一個名為 MyBlogDB.edmx 的 ADO.NET EDM 檔,並設計了一個 Post 實體,如圖 1 所示。

圖 1 在 Visual Studio 2010 中創建的 Post 實體

博客軟體越複雜,需要跟蹤的資料越多,但圖 1 顯示了一些基本資料。按右鍵設計器圖面時,可以選擇“根據模型生成資料庫”,這會顯示將創建的 SQL 檔(在本例中為 MyBlogDB.sql)以及將為創建資料庫而生成的 SQL。按一下“完成”將創建 SQL 檔並將資料庫綁定到我在 EDM 設計器中創建的實體。SQL 的重要部分如圖 2 所示。

圖 2 使用“根據模型生成資料庫”生成的 SQL 代碼

...
USE [MyBlogDB];
GO
...
-- Dropping existing tables
IF OBJECT_ID(N'[dbo].[Posts]', 'U') IS NOT NULL
    DROP TABLE [dbo].[Posts];
GO
...
-- Creating table 'Posts'
CREATE TABLE [dbo].[Posts] (
    [Id] int IDENTITY(1,1) NOT NULL,
    [Title] nvarchar(max)  NOT NULL,
    [PublishDate] datetime  NULL,
    [Content] nvarchar(max)  NOT NULL
);
GO
...
-- Creating primary key on [Id] in table 'Posts'
ALTER TABLE [dbo].[Posts]
ADD CONSTRAINT [PK_Posts]
    PRIMARY KEY CLUSTERED ([Id] ASC);
GO

基本而言,我們做的只是按預期方式根據我們的這個實體創建一個表,並將欄位映射到 SQL 類型。請注意,PublishDate 設置為 NULL,這不是預設設置。我在 EDM 設計器中明確選擇了該設置,因為我希望在沒有發佈日期的情況下也可以正常發佈(某些工具預設情況下不提供發佈日期)。

若要執行此 SQL 並創建資料庫,操作很簡單,只需在 Visual Studio 文字編輯器中按右鍵該 SQL,然後選擇“執行 SQL”。系統會提示您輸入連接資訊和資料庫名稱。因為這是一個新資料庫,所以需要鍵入新名稱(例如 MyBlogDB),然後在出現提示時按一下“確定”以進行創建。創建資料庫以後,可以在伺服器資源管理器中,在 Visual Studio 剛剛為您創建的連接下流覽該資料庫。

為了方便測試,可以在表中直接添加資料,方法是按右鍵 Posts 並選擇“顯示表資料”,這會顯示一個小網格,如圖 3 所示。

圖 3**“顯示表資料”網格可方便測試**

這雖然不是世界上最好的編輯體驗,但是要好過通過編寫 SQL 語句最終得到並運行端到端編輯解決方案(即將討論這種方法,請往下讀!)。

我們已經有了一些資料,現在便可以通過更新 HomeController.cs 來編寫一些 ASP.NET 代碼,從而顯示這些資料(請在 asp.net/mvc/ 閱讀有關 MVC 的更多內容):

...
namespace ODataBloggingSample.Controllers {
  [HandleError]
  public class HomeController : Controller {
    MyBlogDBContainer blogDB = new MyBlogDBContainer();

    public ActionResult Index() {
      return View(blogDB.Posts);
    }

    public ActionResult About() {
      return View();
    }
  }
}

我在此處所做的工作只是創建了 MyBlogDBContainer 類的一個實例,該類是從 MyBlogDB.edmx 檔創建的頂級 ObjectContext 派生類,我們通過它來訪問新資料庫。 (如果您不熟悉實體框架,請參見 msdn.com/data/aa937723.)在對 HomeController 類調用 Index 方法時,表示有人正請求我們的新 Web 應用程式的主頁,我們希望使用該主頁顯示新博客文章,因此將 Posts 集合從資料庫路由到 Home/Index.aspx 視圖的一個實例,我們對該視圖進行了如下修改:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage<IEnumerable<ODataBloggingSample.Post>>" %>

<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <% foreach (var post in Model) { %>
      <h1><%= post.Title %></h1>
      <div><%= post.Content %></div>
      <p><i>Posted <%= post.PublishDate %></i></p>
    <% } %>
</asp:Content>

在這裡,我們將基類更改為採用生成的 Post 類型集合(以及 MyBlogDBContainer 類)來對 Posts 表進行建模。我們還將主頁內容替換為一個 foreach 語句,以顯示每篇文章的標題、內容和發佈日期。

這就是我們需要的全部操作。現在,當我們執行該專案(“調試”|“啟動調試”)時,流覽器會啟動並顯示博客文章(如果您沒有在資料庫中放入更多文章,那麼就只有一篇文章),如圖 4 所示。

图 4 完成的网页

在我告訴您所有這些之後,現在我可以這樣跟您說:OData 的神奇之處在于,只需幾下簡單的滑鼠和鍵盤操作,我便可以公開這些資料的完整程式設計介面,從而可以通過 JavaScript、.NET Framework、PHP 等訪問這些資料。若要親眼見證這種神奇,請在解決方案資源管理器中按右鍵您的專案,選擇“添加”|“新建項”,選擇“WCF 資料服務”,選擇一個名稱(我使用 odata.svc),然後按一下“添加”。您將在一個檔(在本例中為 odata.svc.cs)中獲得一個代碼框架,如果暫時忽略安全性,這個框架如下所示:

using System.Data.Services;
using System.Data.Services.Common;
using ODataBloggingSample;

namespace ODataBloggingSample {
  public class odata : DataService<MyBlogDBContainer> {
    public static void InitializeService(DataServiceConfiguration config) {
      config.SetEntitySetAccessRule("*", EntitySetRights.All);
      config.DataServiceBehavior.MaxProtocolVersion =  
        DataServiceProtocolVersion.V2;
    }
  }
}

請注意,我們引入 MyBlogDBContainer(頂級資料庫訪問類)作為 DataService 類的範本參數,而 DataService 類是伺服器端 WCF 資料服務的核心(請參見 msdn.com/data/bb931106)。通過在 OData 協定中定義的基於 HTTP 謂詞的創建、讀取、更新和刪除 (CRUD) 操作,可以使用 DataService 類輕鬆公開我們的資料庫。對於傳遞給 DataService 的類型,將檢查其用於公開集合的公共屬性。在我們的示例中,實體框架生成的物件上下文類包含非常符合要求的 Posts 集合:

...
namespace ODataBloggingSample {
  ...
  public partial class MyBlogDBContainer : ObjectContext {
    ...
    public ObjectSet<Post> Posts {...}
   ...
  }

  ...
  public partial class Post : EntityObject {
    ...
    public global::System.Int32 Id { get { ... } set { ... } }
    public global::System.String Title { get { ... } set { ... } }
    public Nullable<global::System.DateTime> PublishDate { 
      get { ... } set { ... } }
    public global::System.String Content { get { ... } set { ... } }
    ...
  }
}

請注意,生成的 MyBlogDBContainer 公開一個名為 Posts 的 ObjectSet(只是一種集合),其中包含 Post 類型的實例。 而且,Post 類型經過定義,在 Id、Title、PublishDate 和 Content 屬性與前面所建 Posts 表上的基礎列之間提供映射。

隨著 odata.svc 準備就緒,我們可以流覽到服務文檔,該文檔通過在 URL 中使用資料服務終結點檔的名稱(例如 localhost:54423/odata.svc)來公開物件上下文集合屬性:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<service xml:base="http://localhost:54423/odata.svc/" xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app">
    <workspace>
     <atom:title>Default</atom:title>
      <collection>
        <atom:title>Posts</atom:title>
      </collection>
    </workspace>
</service>

這個檔完全按 AtomPub 規範 (ietf.org/rfc/rfc5023.txt) 定義。 更深入一層,可以看到我們的博客文章在 localhost:54423/odata.svc/Posts 作為一組 Atom 條目公開,如圖 5 所示。

圖 5 作為一組 Atom 條目公開的博客文章

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<feed xml:base="http://localhost:54423/odata.svc/"
  xmlns:d="https://schemas.microsoft.com/ado/2007/08/dataservices"
  xmlns:m=
    "https://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
  xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Posts</title>
  <id>http://localhost:54423/odata.svc/Posts</id>
  <updated>2010-03-15T00:26:40Z</updated>
  <link rel="self" title="Posts" href="Posts" />
  <entry>
    <id>http://localhost:54423/odata.svc/Posts(1)</id>
    <title type="text" />
    <updated>2010-03-15T00:26:40Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="Post" href="Posts(1)" />
    <category term="MyBlogDB.Post"
      scheme=
        "https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">1</d:Id>
        <d:Title>My first blog post</d:Title>
        <d:PublishDate m:type=
          "Edm.DateTime">2010-03-14T00:00:00</d:PublishDate>
        <d:Content>Hi! How are you?</d:Content>
      </m:properties>
    </content>
  </entry>
</feed>

這個檔幾乎是一個徹頭徹尾的傳統 Atom (ietf.org/rfc/rfc4287.txt),唯一不同的就是基於 Microsoft 的 URI,這些 URI 用於將 OData 功能分層到 Atom 之中。具體而言,您需要注意“content”元素中的“properties”元素。您會看出這些屬性就是以前在 Post 實體和相應 Posts 表中定義的相同屬性。此資料包含在由 Atom 定義並通過 CRUD 注釋公開的信封之中,這些注釋本身由 AtomPub 定義,
使您可以通過 HTTP 方法 POST、GET、PUT 和 DELETE 分別執行創建、讀取、更新和刪除操作。問題在於這不是完全傳統的 Atom。例如,如果我們在 Atom 閱讀器(如 Internet Explorer 8)中流覽至 odata.svc/Posts,則標題和內容不會正確顯示,如圖 6 所示。

圖 6 在 Atom 閱讀器中查看博客文章時未顯示標題和內容

您可以看到資料就在其中(請注意,日期是正確的,而且顯示了類別),但是看不到標題和內容。這是因為 Internet Explorer 查找標題和內容的位置(邏輯上為每個條目中的“title”和“content”元素)不包含應包含的資訊。“title”元素為空,而“content”元素採用的格式使 Internet Explorer 無法識別。Internet Explorer 真正需要的格式如下:

<feed ...>
  <title type="text">Posts</title>
  <id>http://localhost:54423/atompub.svc/Posts</id>
  <updated>2010-03-15T00:42:32Z</updated>
  <link rel="self" title="Posts" href="Posts" />
  <entry>
    <id>http://localhost:54423/atompub.svc/Posts(1)</id>
    <title type="text">My first blog post</title>
    <updated>2010-03-15T00:42:32Z</updated>
    ...
    <content type="html">Hi! How are you?</content>
    <published>2010-03-14T00:00:00-08:00</published>
  </entry>
</feed>

請注意,“title”元素包含的資訊過去隱藏在“content”元素中 OData“properties”元素的 Title 屬性之中,“content”元素已由 Content 屬性覆蓋,並且已從 PublishDate 屬性的值添加了“published”元素。當在 Internet Explorer 中查看此資料時,我們獲得的資訊更加符合我們的期望,如圖 7 所示。

圖 7 調整 XML 格式可正確顯示標題和內容

應該指出,這只用于支援我們關心的博客工具。Internet Explorer 不期望獲取客戶清單或發票;它期望獲取標題和發佈日期以及 HTML 內容。對客戶清單和發票進行這種映射有時十分有用,Microsoft 在 WCF 資料服務中的一項名為“友好源”的功能就是一例(請參見 blogs.msdn.com/astoriateam/archive/ 2008/09/28/making-feeds-friendly.aspx)。然而,該功能並不能執行所有操作(具體而言,它不會重新映射 Atom“content”元素),因為 WCF 資料服務團隊希望確保即使是“友好”源也仍然可以使用各種用戶端庫。這樣做的目標是使 OData 源更加友好,而不是放棄 OData 以支援 Atom/AtomPub。

然而在本例中,我們放棄 OData,只使用 WCF 資料服務作為 AtomPub 終結點,這要求在 Atom 與 OData 之間進行映射,如圖 8 所示。

圖 8 Atom 與 OData 之間的映射

奧妙在於:如何進行這種映射?我們顯然已獲取了資料,但是需要將其重新映射到 Atom 屬性,以便 Atom 閱讀器(和編寫器)知道存放資料的位置。這樣做的原因是使 WCF 資料服務仍可以對 .NET Framework 類型進行映射,或通過實體框架對資料庫進行映射。我們要做的只是在 Atom/AtomPub 與 OData 之間進行一點簡單的映射。

本文附帶的代碼示例將一些代碼注入 WCF 管道,該管道恰好允許進行這種消息資料轉換。您可以將它讀入您的核心內容(請查看 ODataBlogging.cs),但我要介紹的是如何使用它。

首先,像以前一樣新建一個 WCF 資料服務終結點,但使用不同的名稱(我使用 atompub.svc)。掛接頂級物件上下文類並公開您需要的所有實體集(像以前一樣),不過還要使用 ODataBloggingServiceBehavior 標記服務類,如下所示:

...
using ODataBlogging;

namespace ODataBloggingSample {
  [ODataBloggingServiceBehavior(typeof(MyBlogDBContainer))]
  [EntityAtomMapping("Posts", "PublishDate", "published")]
  public class atompub : DataService<MyBlogDBContainer> {
    public static void InitializeService(DataServiceConfiguration config) {
      config.SetEntitySetAccessRule("*", EntitySetRights.All);
      config.DataServiceBehavior.MaxProtocolVersion = 
        DataServiceProtocolVersion.V2;
    }
  }
}

這會通過“content”元素內部的嵌套“properties”元素,從傳入的 Atom/AtomPub(例如“title”、“content”和“published”元素)映射到對應的 OData 格式。預設情況下,如果實體上的名稱匹配(忽略大小寫),就會進行映射(以及類型強制)。例如,當公開的某個實體包含 Title 屬性(如我們的 Post 實體)時,該屬性會映射到 Atom“title”元素。

另一方面,如果未進行自動映射,則您可以通過基於實體名稱提供顯式映射來重寫該行為,我們之前將“Posts”集合所含物件的 PublishDate 屬性映射到“published”atom 屬性時就是這樣做的。通過這兩個特性足以將我們的 OData 源轉換為 Atom 源,從而為我們提供功能完整的資料視圖,如圖 7 所示。

此映射不是單向的;它支援所有 HTTP 方法,因此可以使用 AtomPub 協定在 Posts 集合中創建、更新和刪除項以及讀取這些項。這表示您可以配置 Windows Live Writer (WLW) 之類的工具(該工具支援 AtomPub 作為博客 API),並使用該工具對文章進行豐富的文本編輯。例如,如果提供了 atompub.svc 終結點,則在 WLW 中,您可以選擇“日誌”|“添加日誌帳戶”,然後在隨後出現的對話方塊中填寫以下選項:

  • 您使用哪種日誌服務?其他日誌服務
  • Web address of your blog:服務文檔 URL:http://<<伺服器>>:<<埠>>/­atompub.svc
  • 使用者名:<<使用者名>>(必填項,應使用標準 HTTP 方法在 AtomPub 終結點上實現)
  • 密碼:<<密碼>>
  • 要使用的日誌類型:Atom 發佈協定
  • Service document URL:服務文檔 URL:http://<<伺服器>>:<<埠>>/­atompub.svc
  • 博客昵稱:<<您喜歡的任何名稱>>

按一下“完成”,會向您顯示一個用於管理博客文章的 RTF 編輯器,如圖 9 所示。

圖 9 Atom/OData 映射有助於構建 RTF 編輯器以管理您的博客文章

我們在這裡採用了資料服務引擎(該引擎通過將屬性打包到 Atom“content”元素中來支援完整的 CRUD 功能),並進行了一些映射,以使其也支援傳統的 Atom 和 AtomPub。

我用於撰寫本文的小型示例庫(是我同 Phani Raj 一起創作的,他是 Microsoft WCF 資料服務團隊的一名軟體工程師)只具備最簡單的功能,構建真正的博客所需的工作遠不止這麼簡單。下麵列出了我首先想到的、在實際支援 Atom 和 AtomPub 時仍需要進行的一些事項:

  • 映射 Atom 作者元素子元素,如姓名、URI 和電子郵件。
  • 處理圖像(不過 WLW 允許使用 FTP,這樣可能就足夠了)。
  • 公開功能以使 WLW 可識別這些功能。

如果您有興趣深入進行這方面的嘗試,可以參考 Joe Cheng(WLW 團隊成員)撰寫的一系列有關 WLW 中的 AtomPub 支援的博客文章,本文的靈感最開始便是來自這些文章:jcheng.wordpress.com/2007/10/15/how-wlw-speaks-atompub-introduction。jcheng.wordpress.com/2007/10/15/how-wlw-speaks-atompub-introduction.

好好體驗吧!

Chris Sells 是 Microsoft 業務平臺部門的專案經理。他有幾本著作,包括與他人合著的《Programming WPF》(O’Reilly Media,2007 年)、《Windows Forms 2.0 Programming》(Addison-Wesley Professional,2006 年)和《ATL Internals》(Addison-Wesley Professional,1999 年)。在業餘時間,他主持各種會議,並且是 Microsoft 內部產品團隊討論清單上的常客。sellsbrothers.com 上提供了有關 Sells 和他的各種專案的詳細資訊。

衷心感謝以下技術專家對本文的審閱:Pablo Castro