# Azure Storage 上傳、下載檔案
## Azure Storage 架構

先到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`