# Azure Storage 上傳、下載檔案 ## Azure Storage 架構 ![](https://i.imgur.com/O9fzq4W.png) 先到Azure創好 **儲存體帳戶** 側邊欄建立 **容器** 之後可以存放資料 公用存取層級記得選**Blob** 可以在側邊欄的 **存取金鑰** 取得Key的 **連結字串**,存好等等用在Winform ### 用Azure Library呼叫 C#專案加入 ### nuget ```csharp= using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; ``` 建立Azure連結 ```csharp= BlobContainerClient container = new BlobContainerClient(<連結字串>, <容器名稱>); ``` ### 上傳檔案 ```csharp= string blobName = Path.GetFileName(<FileName>); //上傳檔案的名稱 string filePath = <FilePath>; //上傳檔案路徑 // Get a reference to a blob named BlobClient blob = container.GetBlobClient(blobName); // Upload local file blob.Upload(filePath); ``` ### 下載檔案 ```csharp= string downloadFilePath = <FilePath>; //下載的檔案名稱(在雲端上的名稱) BlobClient blob = container.GetBlobClient(downloadFilePath); BlobDownloadInfo download = await blob.DownloadAsync(); using (FileStream downloadFileStream = File.OpenWrite(downloadFilePath)) { await download.Content.CopyToAsync(downloadFileStream); } ``` ------ # REST API的版本 先到[使用共用金鑰授權呼叫 REST API 作業](https://docs.microsoft.com/zh-tw/azure/storage/common/storage-rest-api-auth)下載範本, 雖然我們不是用**共用金鑰**進行授權,但是他給的範例很好用 對上傳、下載檔案分三個部分 **1. 取得檔案列表 2. 上傳檔案 3. 下載檔案** ### 取得檔案列表 先把**AzureStorageAuthenticationHelper.cs**加入到想要的專案裡面 接下來參照[使用共用金鑰授權呼叫 REST API 作業](https://docs.microsoft.com/zh-tw/azure/storage/common/storage-rest-api-auth) 裡面所述可以先取得Blob的列表 先做好基礎的string變數設定 ```csharp= //via REST API configuration //儲存體名稱 static string StorageAccountName = <StorageAccountName>; //keys static string StorageAccountKey = <StorageAccountKey>; //用DataTablet承接資料 public DataTable dt = new DataTable(); ``` 設定Http Request要給的連結 ```csharp= async Task GetBlobListAsyncREST(string storageAccountName, string storageAccountKey, CancellationToken cancellationToken) { dt.Clear(); //因為我想做成refresh DataGridView顯示內容,所以先清理 //這邊直接連結 String uri = string.Format("https://<StorageAccountName>.blob.core.windows.net/<Container>?restype=container&comp=list", storageAccountName); ``` 寫好資料承接的部分 ```csharp= //requestPayload 如果Request要帶資料,就要先把它放到這裡面 Byte[] requestPayload = null; //這邊存Responce XML分析後的結果 List<string> XMLResult = new List<string>(); ``` Http Header的部分有規定,依據不同的功能(上傳、下載...等等),規定一定要放的表頭不一樣 參考[Blob 服務 REST API](https://docs.microsoft.com/zh-tw/rest/api/storageservices/blob-service-rest-api) ```csharp= //取出列表是GET方法 using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri) { Content = (requestPayload == null) ? null : new ByteArrayContent(requestPayload) }) { //這邊是Azure規定要放的表頭 DateTime now = DateTime.UtcNow; httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture)); //Version要查,不同版號會有不同要求 httpRequestMessage.Headers.Add("x-ms-version", "2017-04-17"); ``` 這邊的**AzureStorageAuthenticationHelper.GetAuthorizationHeader**等等會解釋 ```csharp= //這邊是規定要的Authorization表頭 httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(httpRequestMessage.Method, storageAccountName, storageAccountKey, now, httpRequestMessage); // Send the request. using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage, cancellationToken)) { //如果發送成功,會回傳XML檔案 if (httpResponseMessage.StatusCode == HttpStatusCode.OK) { //承接XML String xmlString = await httpResponseMessage.Content.ReadAsStringAsync(); //對XML開始做Parse XElement x = XElement.Parse(xmlString); foreach (XElement container in x.Element("Blobs").Elements("Blob")) { //把Name放到陣列 XMLResult.Add(container.Element("Name").Value); } } else { Console.WriteLine(httpResponseMessage); } } } //再將陣列塞進Datatable後顯示在DateGridView foreach (var item in XMLResult) { DataRow workRow = dt.NewRow(); workRow[0] = item; dt.Rows.Add(workRow); } dataGridView1.DataSource = dt; dataGridView1.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; } ``` **AzureStorageAuthenticationHelper.cs** 裡面有三個方法 1. GetCanonicalizedHeaders 這個會輸出指定格式的版本、時間參數 2. GetCanonicalizedResource 這個會輸出指定格式檔案路徑 3. **GetAuthorizationHeader** 主要看這個方法就好了 依照[使用共用金鑰授權呼叫 REST API 作業](https://docs.microsoft.com/zh-tw/azure/storage/common/storage-rest-api-auth)規範,會把你的Request包裝成指定格式 這個是指定的Request Content,其中signature就是需要包裝的部分 反正就是**電子簽章** ```csharp= Authorization="SharedKey <storage account name>:<signature>" ``` signature要包含的內容如下 ```csharp= StringToSign = VERB + "\n" + Content-Encoding + "\n" + Content-Language + "\n" + Content-Length + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + If-Modified-Since + "\n" + If-Match + "\n" + If-None-Match + "\n" + If-Unmodified-Since + "\n" + Range + "\n" + CanonicalizedHeaders + CanonicalizedResource; ``` 其中CanonicalizedHeaders、CanonicalizedResource 就是標準化後的Header跟檔案路徑 所以來看看**GetAuthorizationHeader**的內容 ```csharp= internal static AuthenticationHeaderValue GetAuthorizationHeader(HttpMethod method, string storageAccountName, string storageAccountKey, DateTime now, HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "") { // 這邊會格式化輸出需要簽名的內容,參照上一格的格式去填資料 String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}", method.ToString(), (method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty : httpRequestMessage.Content.Headers.ContentLength.ToString(), ifMatch, GetCanonicalizedHeaders(httpRequestMessage), GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName), md5); ``` 照理來說這邊的String會輸出像是這種東西,如果有錯看Azure的範本比較準XD ```csharp= GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Thu, 01 Jul 2021 09:23:05 GMTx-ms-version:2017-04-17/<storage name>/<container>comp:listrestype:container ``` 接下來 ```csharp= // 轉換成byte byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature); //SHA256 HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey)); // 轉成64base,這是規定啦 string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)); //最後組成規定格式輸出 AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey", storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes))); return authHV; } ``` 一切正常,會輸出成這樣,這邊是Azure的範例 ```csharp= SharedKey contosorest:uzvWZN1WUIv2LYC6e3En10/7EIQJ5X9KtFQqrZkxi6s= ``` 好,基本上到這邊就可以取得Blob List,並且輸出在DataGridView上 接下來看看上傳跟下載的功能 ### 上傳檔案 按照上面的範例GetBlobListAsyncREST直接拿來改,要注意幾個地方要改 Http Request要給的連結部分直接指定容器、檔案名稱 ```csharp= String uri = string.Format($"https://<storagename>.blob.core.windows.net/<container>/{FileName}", StorageAccountName); ``` Payload的部分要指定 ```csharp= Byte[] requestPayload = File.ReadAllBytes(filePath); ``` 多一個必要的Header,這邊蠻怪的,明明AzureDocs 沒有指定一定要這個Header,但不給會失敗= = ```csharp= httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob"); ``` 記得Http Responce Status Code除了 200(OK) 會多一個 201(Create) 要新增一下 接下來**Authorization**對應的Function也要改 注意要放Content Length,用參數傳就好了 刪刪減減後只剩這樣 ```csharp=\ string MessageSignature = String.Format("PUT\n\n\n{0}\n\n\n\n\n\n\n\n\n{1}{2}", <File.Length>.ToString(), GetCanonicalizedHeaders(httpRequestMessage),GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName)); ``` ### 檔案下載 基本上檔案下載也是GET方法,所以要動的很少 主要注意 這邊的FileName是本地端要儲存的檔案名稱 ```csharp= String uri = string.Format($"https://<storagename>.blob.core.windows.net/<container>/{FileName}", StorageAccountName); ``` Header部分只剩下x-ms-version、x-ms-date、Authorization GetAuthorization的部分要的東西更少,直接把上傳檔案的Content Length砍掉就能用 像是這樣 ```csharp= string MessageSignature = String.Format("GET\n\n\n\n\n\n\n\n\n\n\n\n{0}{1}", GetCanonicalizedHeaders(httpRequestMessage), GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName)); ``` 主要比較麻煩的是從Responce拿回檔案 ```csharp= if (httpResponseMessage.StatusCode == HttpStatusCode.OK || httpResponseMessage.StatusCode == HttpStatusCode.Created) { //讀回來的Responce Content會是Stream類別 Stream s = await httpResponseMessage.Content.ReadAsStreamAsync(); //建一個buffer收下來 byte[] srcBuf = new Byte[s.Length]; s.Read(srcBuf, 0, srcBuf.Length); s.Seek(0, SeekOrigin.Begin); //在讀取後創檔案 using (FileStream fs = new FileStream(FileName, FileMode.Create, FileAccess.Write)) { fs.Write(srcBuf, 0, srcBuf.Length); fs.Close(); } } ``` 接下來就是Refresh DataGridView的部分不寫啦 參考連結: * [Azure Storage MicroSoft Docs](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/storage/Azure.Storage.Blobs) * [Azure Blob REST API 列表 ](https://docs.microsoft.com/zh-tw/rest/api/storageservices/blob-service-rest-api) * [使用共用金鑰授權呼叫 REST API 作業](https://docs.microsoft.com/zh-tw/azure/storage/common/storage-rest-api-auth) * [常見的 REST API 錯誤碼](https://docs.microsoft.com/zh-tw/rest/api/storageservices/common-rest-api-error-codes) ###### tags: `Azure`,`C#`, `REST API`