Windows Azure 儲存體–Blob

31 October 2011 — Written by Sky Chang
#ASP.NET#Azure

在Desktop的環境裡面有硬碟可以用來儲存照片、影片、MP3等等,那Windows Azure當然也要儲存體來進行儲存啦,所以這次介紹的是Windows Azure專門用來存取大型資料的儲存體Blob(Binary Large Object),而在談論之前,也有一個觀念要給大家,無論是Blob或是Table、Queue,其實都是存在於資料庫裏面,所以本機要模擬的時候,也必須要有資料庫才能進行測試喔!

Blob架構

既然Blob是儲存在雲端上,就必須符合雲端的特性,所以架構必定和本機上有所不同,以下是Blob的結構圖,我們可以看到,基本上會分成三層,Account(帳號)、Container(容器)、Blob;畢竟網路那麼多使用者,所以一定是用Account來做區分,而一個Account會有非常多的Container,這些Container是可以自己去自訂的,Container的目的其實就像是利用群組的方式,來設定存取的權限,而Container裡面會有許多Blob,基本上就如下圖。( 其實Container也是物件,官方網站也稱Container為Blob Container ),此外,除了下圖外,Container和Blob還擁有Metadata可以進行描述。

image

Storage Account
  • 所有的Blob是透過此帳號進行存取。
  • 他是整個架構的起點。
  • 一個帳號可以有非常多的Container。
Blog Container
  • 此Container必須在Account內,且此Container幫Blob分了群組。
  • 共享權限的等級,是設在Container裡面,目前支援Public Read和Private,當此權限為Public Read時,任何人都可以進行讀取,而不需要驗證,只有驗證過的對應使用者能進行存取。
  • 容器也有Metadata,大小為8k,是一組<name,value>的形式。
  • Blob Containet擁有可以列出所有Blob的方法,所以可以很輕鬆地去做存取。

Blob

  • Blob儲存在Container裡面,而且每個Blob最高可儲存50G!
  • 每個在Container裡面的Blob都有一個獨一無二的字串名稱。
  • Blob也擁有Metadata可以設定,一樣是8k,也是一組<name,value>的形式。

最後,如上圖,因為是雲端儲存體,所以如果我們要取得Blob,可想而知,就一定是使用url來當作識別的位置啦!如下。

http://<account>.blob.core.windows.net/<container>/<blobname>;

來寫程式吧!

接下來,我們就來撰寫程式看看吧,我相信大家應該也沒有那麼多的錢可以租用Windows Azure服務,老實說,小弟我也沒那麼多錢,所以我的範例都是在本機上面做測試。

首先,要寫Windows Azure,必須先裝SDK,如果還沒裝的可以參考這篇。接下來就在Visual Studio裡面選擇Windows Azure專案。

image

到這邊,就可以選擇Web Role,但小弟我不喜歡在這邊選擇,會利用後續再增加的做法,有興趣的可以參考這篇,如果只想要簡單Demo,也可以直接在這邊選擇ASP.NET Web Role ( 中文翻角色 )。

image

接下來,我們先來看看最後寫完的範例程式執行起來的樣子吧;基本上就是長這樣,這隻程式的主要功能是可以上傳照片,並且可以刪除照片,如果有照片的話,可以從DropDownList可以看到檔名,並且於無資料那邊那個區塊,顯示所有雲端上的照片,這個範例也是Ruddy老師所說的,最經典且簡單的Blob範例,所以小弟我也造樣畫葫蘆,寫了此程式,並用此程式和大家解說。

image

首先我們先來看看HTML這邊的程式碼,基本就是很基本的ASP.NET。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="BlobTest.aspx.cs" Inherits="WebRole1.BlobTest" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

        <asp:FileUpload ID="uploadImage" runat=server />
        <asp:Button ID="uploadBtn" runat="server" Text="上傳" onclick="uploadBtn_Click" />

        <asp:DropDownList ID="imageList" runat="server" AutoPostBack="true"></asp:DropDownList>
        <asp:Button ID="delBtn" runat="server" Text="刪除" onclick="delBtn_Click" />

        <asp:ListView ID="imageView" runat="server">
            <LayoutTemplate>
                <asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
            </LayoutTemplate>
            <EmptyDataTemplate>
                <h2>無資料</h2>
            </EmptyDataTemplate>
            <ItemTemplate>
                <!--
                Eval:Eval是用於單向資料繫結,資料是唯讀的顯示。
                Bind:Bind則是雙向的資料繫當,不但能讀取資料,
                更具有Insert、Update、Delete功能,所以若您需要編輯更新、新增與刪除功能使用本方法。
                語法如下。
                -->
                <img src="<%# Eval("Uri") %>" alt="<%# Eval("Uri") %>" style="float:left" />
            </ItemTemplate>
        </asp:ListView>
    </div>
    </form>
</body>
</html>

為了大家方便,我也做了一張對應圖給大家看。

圖片 1

接下來,我們開始撰寫這個BlobTest.aspx.cs吧( 此ASP.NET的程式名稱為BlobTest.aspx ),首先,既然要存取雲端的Blob,我們當然要先準備一下,存取的方法;首先,我們定義一個GetContainer的方法,來取得CloudBlobContainer,這樣後續就可以利用此方法來取得Container底下的Blob了;就如前面說的,任何的Container都是在Account之下,所以我們第一步,就是先取得Account,因為我們是在本機上模擬與測試,所以我們可以使用CloudStorageAccount這個靜態類別的DevelopmentStorageAccount這個靜態方法來取得開發用的Account;有了Account,就可以利用Account來取得Client來進行後續的處理,最後,我們就可以利用Client來取得Container ( 這個Container的名稱就是myContainer );到這邊還滿容易理解的,但是有人可能會有疑問,明明沒看到建立mycontainer這個Container的程式碼阿?沒錯,如果第一次執行的時候,到這邊的確還沒有在資料庫裡建立起mycontainer這個Container,我們只是利用client的GetContainerReference(“mycontainer”)這個方法來取得CloudBlobContainer這個物件,並且設定CloudBlobContainer裡面的Name屬性為mycontainer,後續我們才會於資料庫建立Container。

//取得Container
private CloudBlobContainer GetContainer()
{
    //取得Developer用的Storage Account。
    CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
    //取得Storage的Client
    CloudBlobClient client = account.CreateCloudBlobClient();
    //取得Container關聯。
    return client.GetContainerReference("mycontainer");
}

有了取得Container這個方法,接下來,我們就來實際撰寫建立容器的方法,第一行滿簡單的,就是取回CloudBlobContainer這個容器,而第二行,我們就利用取得回來的CloudBlobContainer物件來建立一個Container,CreateIfNotExist(),這個方法很好玩,如果目前資料庫裡面已經有了名為mycontainer的這個容器,他就不會再建立了,如果沒有,就會在資料庫裏面建立一個。

//建立容器
private void CreateContainerExists()
{
    CloudBlobContainer container = this.GetContainer();
    //假如沒有這個Container,就建立一個。
    container.CreateIfNotExist();
}

我們可以看到,如果程式執行到這邊,就會建立了一個mycontainer這個container。

( 這是一個很好用的Azure Storage 瀏覽工具 )

image

完成了存取後,我們來處理一下刷新Image這個方法,這個方法的用處是,當有上傳,或是刪除動作的時候,可以呼叫這個方法來重新刷新頁面上的控制項,詳細可以看程式碼的註解。

//刷新Image
private void RefreshImage()
{
    //ListBlobs可以取回此Container所有的Blobs,而後面可以傳入BlobRequestOptions這個物件,
    //這個物件可以設定,傳回來的條件。
    //這邊條件設定的第一個是列出所有的Blob(UseFlatBlobListing = true)
    //並且抓取所有的Blob對象 ( BlobListingDetails = BlobListingDetails.All )
    imageView.DataSource = this.GetContainer().ListBlobs(new BlobRequestOptions() { UseFlatBlobListing = true, BlobListingDetails = BlobListingDetails.All });
    imageView.DataBind();
    //每次刷新時,也清除DropDownList內的資訊。
    imageList.Items.Clear();
    var blobList = this.GetContainer().ListBlobs();
    foreach (var item in blobList)
    {
        //每個Blob都有一個Uri屬性,這裡是取得Blob的檔案名稱。
        imageList.Items.Add(item.Uri.Segments[3].ToString());
    }

    //dropDownListItem其實是紀錄目前dropDownList選了哪一個item。
    //這個變數是此類別的屬性,最後整體程式碼的時候會看到。
    imageList.SelectedIndex = dropDownListItem;

}

接下來,我們來撰寫存Image這個方法,這個方法會帶四個參數進來,第一個會傳入id,後續我們會利用Guid.NewGuid.ToString()來產生id,而第二個會傳入檔案名稱,第三個會傳入檔案的型態( image/jpeg ),第四個則是圖片binary的資料;程式碼的第一行,會使用GetBlobReference來取得Blob物件,這個方法和之前的GetContainerReference一樣,並不會馬上寫到資料庫,指示先將Blob物件準備好,後面的部分,就只是很簡單的設定檔案型態、定義MetaData,最後使用UploadByteArrary將資料寫到資料庫。

//存Image
private void SaveImage(string id, string fileName, string contentType, byte[] data)
{
    //利用GetBlobReference來取得Blob,第一個參數會帶進檔案名稱,
    //而此檔案名稱會化成Uri的一部分。
    CloudBlob blob = this.GetContainer().GetBlobReference(fileName);
    //檔案型態
    blob.Properties.ContentType = contentType;

    //定義MetaData
    NameValueCollection metadata = new NameValueCollection();
    metadata["Id"] = id;
    metadata["Filename"] = fileName;

    blob.Metadata.Add(metadata);
    blob.UploadByteArray(data);
}

最後我們要處理的就是刪除,刪除的原理很簡單,利用DropDownList所選取到的檔名去和娶回來的BlobList來比對,如果相同,就將此Blob移除。

//刪除
protected void delBtn_Click(object sender, EventArgs e)
{
    if (this.GetContainer() == null) return;

    var blobList = this.GetContainer().ListBlobs();
    foreach (var item in blobList)
    {
        string deleteImage = item.Uri.Segments[3].ToString();
        //判斷DropDownList所選擇的item名稱和Blob名稱是否相同,
        //相同就刪除。
        if (deleteImage == imageList.SelectedValue)
        {
            //利用GetBlobReference來取得Blob
            CloudBlob blob = this.GetContainer().GetBlobReference(item.Uri.ToString());
            blob.DeleteIfExists();

            //刪除完後刷新一下。
            RefreshImage();
            return;
        }
    }
}

最後附上完整的程式碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using System.Net;
using System.Collections.Specialized;
using System.Configuration;

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WebRole1
{
    public partial class BlobTest : System.Web.UI.Page
    {
        int dropDownListItem = 0;
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                //First
                this.CreateContainerExists();
                dropDownListItem = imageList.SelectedIndex;
                this.RefreshImage();
            }
            else
            {
                dropDownListItem = imageList.SelectedIndex;
                this.RefreshImage();
            }
        }

        //上傳
        protected void uploadBtn_Click(object sender, EventArgs e)
        {
            if (uploadImage.HasFile)
            {
                //SaveImage(string id, string fileName, string contentType, byte[] data)
                this.SaveImage(Guid.NewGuid().ToString(), uploadImage.FileName, uploadImage.PostedFile.ContentType, uploadImage.FileBytes);
                RefreshImage();
            }
        }

        //取得Container
        private CloudBlobContainer GetContainer()
        {
            //取得Developer用的Storage Account。
            CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
            //取得各種Storage的Client
            CloudBlobClient client = account.CreateCloudBlobClient();
            //取得Container。
            return client.GetContainerReference("mycontainer");
        }

        //確保容器存在
        private void CreateContainerExists()
        {
            CloudBlobContainer container = this.GetContainer();
            //假如沒有這個Container,就建立一個。
            container.CreateIfNotExist();
        }

        //刷新Image
        private void RefreshImage()
        {
            //ListBlobs可以取回此Container所有的Blobs,而後面可以傳入BlobRequestOptions這個物件,
            //這個物件可以設定,傳回來的條件。
            //這邊條件設定的第一個是列出所有的Blob(UseFlatBlobListing = true)
            //並且抓取所有的Blob對象 ( BlobListingDetails = BlobListingDetails.All )
            imageView.DataSource = this.GetContainer().ListBlobs(new BlobRequestOptions() { UseFlatBlobListing = true, BlobListingDetails = BlobListingDetails.All });
            imageView.DataBind();
            //每次刷新時,也清除DropDownList內的資訊。
            imageList.Items.Clear();
            var blobList = this.GetContainer().ListBlobs();
            foreach (var item in blobList)
            {
                //每個Blob都有一個Uri屬性,這裡是取得Blob的檔案名稱。
                imageList.Items.Add(item.Uri.Segments[3].ToString());
            }

            //dropDownListItem其實是紀錄目前dropDownList選了哪一個item。
            //這個變數是此類別的屬性,最後整體程式碼的時候會看到。
            imageList.SelectedIndex = dropDownListItem;

        }

        //存取Image
        private void SaveImage(string id, string fileName, string contentType, byte[] data)
        {
            //利用GetBlobReference來取得Blob,第一個參數會帶進檔案名稱,
            //而此檔案名稱會化成Uri的一部分。
            CloudBlob blob = this.GetContainer().GetBlobReference(fileName);
            //檔案型態
            blob.Properties.ContentType = contentType;

            //定義MetaData
            NameValueCollection metadata = new NameValueCollection();
            metadata["Id"] = id;
            metadata["Filename"] = fileName;

            blob.Metadata.Add(metadata);
            blob.UploadByteArray(data);
        }

        //刪除
        protected void delBtn_Click(object sender, EventArgs e)
        {
            if (this.GetContainer() == null) return;

            var blobList = this.GetContainer().ListBlobs();
            foreach (var item in blobList)
            {
                string deleteImage = item.Uri.Segments[3].ToString();
                //判斷DropDownList所選擇的item名稱和Blob名稱是否相同,
                //相同就刪除。
                if (deleteImage == imageList.SelectedValue)
                {
                    //利用GetBlobReference來取得Blob
                    CloudBlob blob = this.GetContainer().GetBlobReference(item.Uri.ToString());
                    blob.DeleteIfExists();

                    //刪除完後刷新一下。
                    RefreshImage();
                    return;
                }
            }
        }
    }
}

其實,這就是Blob的範例應用,雖然看起來程式碼很長,但其實還滿簡單的。

參考資料

Sky & Study4.TW