本文章是由機器翻譯。

預測:多雲

將內容從 SharePoint 推送到 Windows Azure 存儲

Joseph Fultz

下載代碼示例

本月我的合著者是我的同事 Shad Phillips,他協助我完成了一個最新專案,即我與我的一個客戶一起證明將 SharePoint 2010 用作應用程式平臺這一概念。另外,客戶的一個職員問我是否能想出一種合理的方式以從 SharePoint 獲得批准的內容併發布這些內容,使企業網路外部的人員能使用它們。

客戶當前的基礎結構不支援外部內容(可下載的文檔和視頻)。使用 Windows Azure 完成大量工作後,我立即想到將內容推送到 Windows Azure 存儲這個操作併入為工作流的一部分應是非常簡單的,然後根據需要公開發布內容或提供對受限內容的基於租約的訪問。

明確這一點後,我與我的同事 Shad 進行了商談,他之前在解決類似的問題時實現了將庫中的 SharePoint 文檔存檔到 Windows Azure 存儲的示例方法。雖然該解決方案的意圖與我的目標不同,但機制是一樣的。本月,Shad 和我將逐步演示一個將內容從 SharePoint 推送到 Windows Azure 存儲的示例實現,並介紹一些有關針對檔的租約存取控制的內容。

方案和設置

具體而言,我們開發了一個自訂功能,使用者可利用此功能有選擇地將文檔從 SharePoint 推送到 Windows Azure 存儲。出於某個無法解釋的原因,使用者通常在其文檔被移動且未提供查找這些文檔的連結時不願執行上述操作,因此我們在文件庫中保留了一個指向雲位置的連結,文檔在該位置的行為與在非雲位置的行為相同。

必需的軟體:

  • Visual Studio 2010
  • Microsoft SharePoint 2010
  • Windows Azure SDK
  • Windows Azure 開發存儲服務

SharePoint 2010 網站和文件庫配置

在此方案中,我們使用團隊網站範本創建了一個 SharePoint 網站。我們在共用文件庫中創建了一個列,該列可用於將專案標記為已存檔到 Windows Azure。利用可通過功能區訪問的庫設置來完成此操作。進入“庫設置”後,我們創建一個包含屬性的列,如圖 1 所示。

圖 1 團隊網站範本上的列設置

在“高級設置”中,我們還為“允許管理內容類別型”設置選擇“是”。

我們將其用作已命名為“文檔連結”的內容類別型的一部分。接下來,我們創建了此內容類別型的實例來表示指向已存檔文檔的連結,如圖 2 所示。

圖 2 我們的新“文檔連結”內容類別型

在將列和內容類別型添加到文件庫後,我們上載了一個名為 Services SOW.docx 的示例 Word 文檔。

SharePoint 2010 Web.config

若要連接到雲,我們需要獲取與 Windows Azure 連接時所需的設置。在此示例中,我們使用了開發存儲並將鍵添加到 web.config 中的 <appSettings>元素,如圖 3 所示。

圖 3 在 Web.config 中添加鍵

SharePoint 專案

幸運的是,對於使用了 Visual Studio 2010 的 SharePoint 2010 來說,創建、調試和發佈新功能是絕佳的開發人員體驗。我們創建了一個 SharePoint 功能(有關此功能的詳細資訊,請參閱 msdn.microsoft.com/library/bb861828(office.12)),此功能在文件庫的專案操作下拉式功能表中添加了一個自訂操作。使用者將按一下它來使用存檔功能。

首先,我們使用空 SharePoint 專案範本創建一個名為 MSSAzureArchive 的解決方案(參見圖 4)。

圖 4 Visual Studio 2010 中的專案選擇

接下來,我們指定了網站和安全級別以進行調試。由於代碼需要進行外部調用,而沙箱解決方案不允許這樣做,因此我們決定選擇“部署為場解決方案”。需要為 Microsoft.Windows.Azure.StorageClient 和 System.Web 的專案添加引用。接下來,我們使用 EmptyElement 範本將一個“空元素”項添加到專案中,並將該項命名為 AzureStorageElement。我們添加了一個 <CustomAction/>元素,以便將新的操作項添加到文件庫項的上下文功能表中(參見圖 5)。

圖 5 通過“添加新項”來添加 AzureStorageElement

自動將一個名為 Feature1 的新功能添加到專案中,我們已將其重命名為 MSSAzureArchive。我們已將添加的 AzureStorageElement 的 Elements.xml 檔的內容替換為以下內容:

<?xml version="1.0" encoding="utf-8"?>
 <Elements xmlns="https://schemas.microsoft.com/sharepoint/">
   <CustomAction
     Id="UserInterfaceCustomActions.ECBItemToolbar"
     RegistrationType="List"
     RegistrationId="101"
     Location="EditControlBlock"
     Sequence="106"
     Title="Azure Storage">
     <UrlAction Url="~sitecollection/
       _layouts/MSSAzureArchive/
       AzureStorage.aspx?ItemUrl={ItemUrl}" />
   </CustomAction>
 </Elements>

對於缺乏經驗的 SharePoint 開發人員,圖 6 顯示了部分 <CustomAction/>屬性的簡要說明(有關 <CustomAction/>元素及其屬性的詳細資訊,請參閱 msdn.microsoft.com/library/ms460194)。

圖 6 **<CustomAction/>**元素的屬性

屬性 功能
ID 唯一識別碼。
Location 指定元素應在 SharePoint UI 中出現的位置。在此示例中,項功能表 (EditControlBlock) 是相對於功能區等 UI 的所需位置。
Sequence 指定操作的排序優先順序。

請注意 UrlAction 元素的 Url 屬性;這是一個為處理存檔文檔命令而發生的導航操作。SharePoint 根據此配置確定了將功能置於 UI 中的哪個位置,以及在某人按一下該功能時應執行的操作。SharePoint 將導航到我們創建的用來處理選定文檔的存檔的頁面。通過此頁面,使用者可以為項選擇一個目標存儲容器或創建一個新存儲容器,因此我們需要將“應用程式頁”項添加到專案。我們再次使用 SharePoint 2010 範本來選擇“應用程式頁”範本,並將其命名為 AzureStorage.aspx(參見圖 7)。

圖 7 在 SharePoint 2010 中添加新頁

由於此示例並不是為了給任何人留下精美 UI 設計這一印象,因此我們只添加了完成此工作所需的最少控制項。在頁面標記的 <asp:Content>元素中,我們添加了代碼,如圖 8 所示。

圖 8 添加所需的最少控制項

Document to Archive:
<asp:Label ID="fileName" runat="server" ></asp:Label>   <br/>   
Choose Azure Container:
<asp:DropDownList ID="azureContainers" runat="server"  
  Visible="true"></asp:DropDownList>   
<asp:TextBox id="newContainerName" runat="server" Visible="false"></asp:TextBox>
<asp:Button ID="saveContainer" runat="server" Text="Save Container" 
  OnClick="SaveContainer_Click" Visible="false"></asp:Button>
<br />
<asp:Button ID="createContainer" runat="server" Text="Create New Container" 
  OnClick="CreateContainer_Click" />
<br/>
<asp:Button ID="archiveFile" runat="server" Text="Archive File" 
  OnClick="Archive_Click" />       
<br/>
<asp:Label ID="errMessage" runat="server" Text=""></asp:Label>

接下來,我們編輯了隱藏代碼,將 UI 元素連接到一些代碼以便與 Windows Azure 存儲進行通信,並且呈現了相關資訊。 我們在頁的載入事件中初始化了雲存儲用戶端,並使用以前的 web.config 設置獲取了可用容器(參見圖 9)。

圖 9 初始化雲存儲用戶端

protected void Page_Load(object sender, EventArgs e)
{
  this.InitializeCloudStorage();
  if (!IsPostBack)
  {
    this.GetContainers();
  }
}
private void GetContainers()
{
  IEnumerable<CloudBlobContainer> blobContainers =  
    cloudBlobClient.ListContainers();
  this.azureContainers.DataSource = blobContainers;
  azureContainers.DataTextField = "Name";
  this.azureContainers.DataBind();
  if (azureContainers.Items.Count < 1)
  {
    ListItem defaultContainer = new ListItem(defaultContainerName);
    defaultContainer.Selected = true;
    azureContainers.Items.Add(defaultContainer);
  }
}

由於此處的重點是存檔功能,因此我們將集仲介紹它。 可通過下載來獲取其他代碼 (code.msdn.microsoft.com/mag201012Cloudy)。 我們為 archiveFile 按鈕添加了按一下處理常式並將它與 Archive_Click 函數連接在一起。 我們可根據 UrlAction 元素來檢索項的路徑。 在該按一下函數中,使用物件模型從 SharePoint 提取項,檢查它是否已存檔 - 如果未存檔,則將它上載到選定容器(參見圖 10)。

圖 10 用於從 SharePoint 提取項的按一下函數的代碼

protected void Archive_Click(object o, EventArgs e)
{
  try
  {
    webSite = SPContext.Current.Web;
    filePath = webSite.Url.ToString() + 
    Request.QueryString["ItemUrl"].ToString();
    fileToArchive = webSite.GetFile(filePath);
    string sArchived = fileToArchive.Item["IsArchived"].ToString(); 
    bool isArchived = Convert.ToBoolean(sArchived);
    if (isArchived)
    {
      errMessage.Text = "This document has already been archived!";
    }
    else
    {
      string newGuid = Guid.NewGuid().ToString();
      string uniqueBlobName = string.Format(newGuid + "_" + 
        fileToArchive.Name.ToString());
      blobContainer = cloudBlobClient.GetContainerReference(
        this.azureContainers.SelectedValue);
      blobContainer.CreateIfNotExist();
      cloudBlob = blobContainer.GetBlockBlobReference(uniqueBlobName);
      cloudBlob.UploadByteArray(fileToArchive.OpenBinary());

在將項上載到存儲後,將創建一個類型為“文檔連結”的新存檔項來代替原始文檔,且原始文檔將被刪除。 如果這是一個發佈示例而非存檔示例,則可能不會刪除原始項,而是使用已發佈版本的連結將其標記為已發佈。 原始項用於獲取目的文件庫和原始文檔的路徑。 通過添加 IsArchived 屬性並分配值“true”將新項標記為已存檔。首先,我們執行一些操作來獲取部分所需值,然後創建新項並將這些值分配給該項,如下所示:

SPDocumentLibrary docLib = 
  fileToArchive.DocumentLibrary;
Hashtable docProperties = new Hashtable();
docProperties["IsArchived"] = true;
string docLibRelPath = 
  docLib.RootFolder.ServerRelativeUrl;
string docLibPath = string.Empty;
webSiteCollection = SPContext.Current.Site;
docLibPath = 
  webSiteCollection.MakeFullUrl(docLibRelPath);
string azureURL = cloudBlob.Uri.ToString();

函數 BuildLinkToItem 使用 Windows Azure 存儲中項的路徑創建了內容類別型“文檔連結”的實例。 該內容類別型實例將添加到庫中,作為通過 SharePoint UI 從 Windows Azure 存儲檢索項的連結,如下所示:

string azureStub = this.BuildLinkToItem(azureURL).ToString();
  SPFile newFile = webSite.Files.Add(documentPath,     
    UTF8Encoding.UTF8.GetBytes(azureStub), docProperties, true);
  SPListItem item = newFile.Item;
  item["Content Type"] = "Link to a Document";
  SPFieldUrlValue itemUrl = new SPFieldUrlValue();
  itemUrl.Description = fileToArchive.Name;
  itemUrl.Url = azureURL;
  item["URL"] = itemUrl;
  item["IsArchived"] = true;
  item.Update();
  fileToArchive.Delete();

使用完成的代碼保存文檔,將其移動並替換為指向 Windows Azure 存儲的連結後,就該側重于解決方案的生成和部署了。我們按兩下 Package.package 檔以顯示包設計器,然後選擇螢幕底部的“高級”選項卡。我們將所需的包程式集添加到此處,以便包含 Microsoft.WindowsAzure.StorageClient.dll。為了簡化該示例,我們將部署目標設置為 GlobalAssemblyCache。我們通過以下操作來確保開發存儲處於運行狀態:導航到伺服器資源管理器,按一下“Windows Azure 存儲”節點,然後按一下“(開發)”節點。

顧不了這麼多了,我們按 F5 鍵來構建、部署、附加到進程並啟動流覽器會話以開始調試功能。我們導航回到之前提及的共用文件庫,並打開附加到之前載入的文檔的下拉式功能表。在下拉式功能表中,我們選擇了新元素“Azure 存儲”,我們可通過它進入自訂應用程式頁以選擇目標容器(參見圖 11)。

圖 11 選擇 Windows Azure 存儲元素

雖然我們之前已在此頁上創建了新容器,但我們將改用已創建的文檔容器並按一下“存檔檔”按鈕來執行前面的代碼(參見圖 12)。

圖 12 選擇容器

在將檔存檔到 Windows Azure 存儲後,我們便導航回到共用文件庫。不會顯示文檔,而是顯示替換 Services SOW.docx Word 文檔的“文檔連結”項(參見圖 13)。

圖 13 SharePoint 文件庫中的“文檔連結”

當我們查看項的屬性時,會看到與內容類別型相關的欄位,特別是指向文檔在 Windows Azure 存儲中當前位置的 URL(參見圖 14)。

圖 14 連結屬性

我們可以通過按一下“文檔連結”直接從 Windows Azure 存儲打開文檔。可以使用 URL 屬性直接訪問它或通過其他代碼或 UI 訪問它。例如,如果我們仍需要通過 SharePoint 索引服務為這些項建立索引,則可以創建一個知道如何處理“文檔連結”內容類別型以確保為內容建立正確索引的自訂 IFilter。

實現將 SharePoint 文件庫中的內容存檔到 Windows Azure 存儲容器已不成問題,這樣,對於未經身份驗證的請求,我們現在只能公開訪問甚至無法訪問已存檔文檔。

發佈時的存取控制

我之前說過,促使我與 Shad 談論其存檔部分的原因是使用 Windows Azure 存儲作為一種提供用於訪問已經過審閱和批准的內容的公眾登錄點的方式。在我考慮的示例中,沒有必要包含任何存取控制,因為文檔將與每個人共用。但仍留出了幾分鐘時間供使用者提問題:“如果我們需要發佈一些內容且只允許某些人(如供應商、客戶或員工)訪問這些內容,該怎麼辦?”類似這樣的任務通常在公司內部採用以下方式完成:將相關人員加入公司域,或以某種方式將他們聯合起來以便通過使用者名和密碼問題對其進行標識。此處的示例不是這種情況,客戶並不是真的想設置一些應用程式層或前端來控制訪問;開發前端會增加實現成本,從而降低價值。

一種解決方案是在 Blob 上使用 SharedAccessPolicy。容器和容器中的 Blob 會使用少量代碼將其 PublicAccess 設置為“關閉”,您在執行 Windows Azure 存儲開發時可能要編寫這些代碼。以下代碼示例演示如何將 PublicAccess 設置為“關閉”,但考慮到容器上的 SharedAccess,我應生成並分發簽名:

BlobContainerPermissions permissions = new BlobContainerPermissions();
  permissions.PublicAccess = BlobContainerPublicAccessType.Off;
  SharedAccessPolicy accesspolicy = new SharedAccessPolicy();
  accesspolicy.Permissions = SharedAccessPermissions.Read;
  permissions.SharedAccessPolicies.Add("Read", accesspolicy);
  BlobContainer.SetPermissions(permissions);

如果我們直接請求存儲容器中的資源,則會收到消息“未找到頁面 404”。 在我們上載 Blob 時,我們對 Blob 自身執行了少量類似工作,但我們創建了一個允許讀取的 SharedAccessPolicy,為它設置了一個過期時間,並向後請求“共用訪問簽名”,與以下內容類別似:

SharedAccessPolicy policy = new SharedAccessPolicy();
policy.Permissions = SharedAccessPermissions.Read;
policy.SharedAccessExpiryTime = DateTime.Now.AddDays(5);
string SharedAccessSignature = destBlob.GetSharedAccessSignature(policy));

調用 GetSharedAccessSignature 將返回一個與以下內容類別似的字串:

?se=2010-08-26T18%3A22%3A07Z&sr=b&sp=r&sig=WdUHKvQYnbOcMwUdFavn4QS0lvhAnqBAnVnC6x0zPj8%3D

如果我將查詢字串連接到 Blob 的 URI 的結尾處,則應將它收回,前提是未過期。有關簽名和共用訪問策略的詳細資訊,請參閱 msdn.microsoft.com/library/ee395415

為了解決此問題,我將生成簽名並提供到期日期較長的已簽名 URI,這樣便能在上載時輕鬆創建它們,然後存儲指向已發佈文檔的連結的清單。為了提供更好的安全性並為單個使用者提供短時間的訪問權,我需要一個使用者介面。通過此使用者介面,使用者可以請求對一個或多個資源的訪問權,並獲得將提供短期訪問權的已簽名 URI。

與雲混合

Shad 和我在這裡使用了一個常規實現來討論兩個不同的方案。這對兩個方案都特別有用,因為我們需要雲提供的特殊功能(可伸縮的、可靠的且可擴展的存儲),無需做太多的設置工作,而且只需為使用的內容付費。我們想表達的主要意思是,當專業人員希望為我們的客戶(內部或外部)創建解決方案時,我們的解決方案概念不必單獨包含在雲中或內部部署中。可輕鬆地將兩者混合在一起。隨著服務更新的應用,將公司網路和雲網路混合在一起會變得越來越簡單。我希望以後的混合能夠達到二者大同小異的程度。當您查看軟體或業務系統的解決方案時,可以暫停下來花時間思考一下“雲中是否有可説明我的內容?”

Joseph Fultz* 是達拉斯 Microsoft 技術中心的架構師,協助企業客戶和 ISV 設計和製作軟體解決方案以滿足商業和市場需求。他在 Tech·Ed 及類似的內部培訓活動中做過講座。*

Shad Phillips* 是達拉斯 Microsoft 技術中心的架構師,協助企業客戶和合作夥伴設計和部署基於 Microsoft SharePoint 2010 構建的企業內容管理解決方案。*

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