ASP.NET Web API上傳大檔案至Windows Azure Blob儲存體

學好Windows Azure必看的一本書

Windows Azure Platform應用程式開發教戰手札(第二版)
圖片來源:http://books.gotop.com.tw/v_ACL036700

首先,如果各位對Windows Azure不是很熟悉的話,我推薦小朱的《Windows Azure Platform應用程式開發教戰手札(第二版)》。

ASP.NET Web API介接服務

「介接服務」的意思是,在Web API裡,我們可以拿別人的服務來成為我的服務,Client只是使用者,他不需要知道細節。以「上傳檔案」而言,當Client上傳一個檔案,你可以儲存在本機,也就是進行File I/O的動作,你可以儲存在Database裡,也就是進行Database I/O動作。

在以下範例中,我們要在Web API裡去儲存檔案至Windows Azure的Blob儲存體中,也就是說,我們拿Windows Azure Storage服務來當成我們服務內容之一。

這裡的概念很重要,例如,我們公司有申請HiCloud服務,在HiCloud的服務內容中有一項HiCloud S3服務,它是一項和Amazon S3一模一樣服務。S3是Simple Storage Service的縮寫,基本上,它也是一項RESTful Service,透過RESTful Service提供“檔案存取”的功能。

也就是說,我們不一定只能選擇Windows Azure Storage,只要對方有提供API給我們存取,我們都可以拿來當成Web API服務的一部分,而Client並不需要知道這些細節。

使用Windows Azure Storage Emulator測試

那天在twMVC#8課程現場並無網路,而我有辦法進行此一範例的Demo要拜謝Windows Azure Storage Emulator,如果各位有安裝Web Tools 2012.2,預設就會安裝Windows Azure SDK,不然就必須到Windows Azure官網去下載安裝(VS 2012 SDK | VS 2010 SDK)。Windows Azure SDK會在本機安裝模擬器(Emulator),讓我們就算在沒有網路的環境,也能進行Windows Azure相關程式碼的測試。

在使用要切換本機與Windows Azure正式環境也非常容易,只需要透過web.config裡的<appSettings>段落去進行組態切換即可。

     <!-- 重點一:連線Blob資訊 -->
     <add key="CloudStorageConnectionString" value="UseDevelopmentStorage=true" />
     <!--
     <add key="CloudStorageConnectionString" value="DefaultEndpointsProtocol=http;AccountName=NAME;AccountKey=KEY"/>
     -->
  

Key名稱可自訂,value是才是重點,使用本機的儲存體模擬器必須指定“UseDevelopmentStorage=true”,當完成開發後,只需要切換組態至Windows Azure的設定,會立即馬上改為Windows Azure Storage服務。

Windows Azure Blob資訊
Windows Azure Blob金鑰資訊

上傳檔案至Blob

一、首先在Models目錄新增一個Files類別,用來儲存檔案資訊:

  ''' <summary>
  ''' 儲存上傳檔案資訊
  ''' </summary>
  Public Class FileDetails
      Property Name As String
      Property Size As Long
      Property ContentType As String
      Property Location As String
  End Class
  

二、新增一個Helpers目錄,新增BlobHelper輔助類別,用以幫忙我們快速取得容器資訊。這裡注意的是,必須先將「Microsoft.WindowsAzure.Configuration」與「Microsoft.WindowsAzure.StorageClient」加入參考。

Windows Azure 命名空間
  Imports Microsoft.WindowsAzure
  Imports Microsoft.WindowsAzure.StorageClient

  ''' <summary>
  ''' Windows Azure Blob輔助方法
  ''' </summary>
  ''' <remarks></remarks>
  Public Class BlobHelper
      ''' <summary>
      ''' 取得與設定Azure Blob容器
      ''' </summary>
      Public Shared Function GetWebApiContainer() As CloudBlobContainer
          ' 從appSettings裡的連線字串取得Storage的AccountName
          Dim storageAccount As CloudStorageAccount = CloudStorageAccount.Parse(
              CloudConfigurationManager.GetSetting("CloudStorageConnectionString"))

          ' 建立Blob client
          Dim blobClient As CloudBlobClient = storageAccount.CreateCloudBlobClient()

          ' 取得容器(container)的參考
          ' 容器名稱必須小寫,請參考:http://msdn.microsoft.com/en-us/library/windowsazure/dd135715.aspx
          Dim container As CloudBlobContainer = blobClient.GetContainerReference("webapicontainer")

          ' 確認容器是否存在,不存在就建立一個新容器
          container.CreateIfNotExist()

          ' 啟用公開存取Blob
          Dim permissions As BlobContainerPermissions = container.GetPermissions()
          If permissions.PublicAccess = BlobContainerPublicAccessType.Off Then
              permissions.PublicAccess = BlobContainerPublicAccessType.Blob
              container.SetPermissions(permissions)
          End If

          Return container
      End Function
  End Class   
  

三、新增一個Providers目錄,新增一個AzureBlobStorageMultipartProvider類別。

  Imports System.Collections.Generic
  Imports System.IO
  Imports System.Net.Http
  Imports System.Threading.Tasks
  Imports Microsoft.WindowsAzure.StorageClient

  ''' <summary>
  ''' 繼承MultipartFileStreamProvider,實作ExecutePostProcessingAsync方法。
  ''' </summary>
  ''' <remarks>http://msdn.microsoft.com/zh-tw/library/system.net.http.multipartfilestreamprovider(v=vs.108).aspx</remarks>
  Public Class AzureBlobStorageMultipartProvider
      Inherits MultipartFileStreamProvider

      Private _container As CloudBlobContainer
      Public Property Files As List(Of FileDetails)
      Sub New(container As CloudBlobContainer)
          ' Path.GetTempPath():回目前使用者的暫存資料夾的路徑。
          MyBase.New(Path.GetTempPath())

          _container = container
          Files = New List(Of FileDetails)()
      End Sub

      Public Overrides Function ExecutePostProcessingAsync() As Task
          ' 上傳檔案到Azure Blob儲存體,然後從本機移除它們
          ' FileData:http://msdn.microsoft.com/zh-tw/library/system.net.http.multipartfiledata(v=vs.108).aspx
          For Each Data As MultipartFileData In FileData
              Dim fileName As String = Path.GetFileName(Data.Headers.ContentDisposition.FileName.Trim(""""))

              ' 取得加入Blob
              Dim blob As CloudBlob = _container.GetBlobReference(fileName)
              ' 指定上傳MediaType
              blob.Properties.ContentType = Data.Headers.ContentType.MediaType
              ' 上傳
              blob.UploadFile(Data.LocalFileName)
              ' 刪除本機檔案
              File.Delete(Data.LocalFileName)
              ' 加入Files集合
              Files.Add(New FileDetails With {
                        .ContentType = blob.Properties.ContentType,
                        .Location = blob.Uri.AbsoluteUri,
                        .Name = blob.Name,
                        .Size = blob.Properties.Length})
          Next

          Return MyBase.ExecutePostProcessingAsync()
      End Function
  End Class
  

這裡我們繼承MultipartFileStreamProvider類別並覆寫ExecutePostProcessingAsync方法。MultipartFileStreamProvider類別在《ASP.NET MVC 4網站開發美學》第7-124頁 上傳檔案一節我們有進行討論,讀者可以配合著看以加強瞭解。

這裡基本上是使用Windows Azure SDK裡的方法,將上傳的檔案寫入Blob儲存體之中。

四、新增FilesController類別,新增Post方法與Get方法。

  Imports System.Collections.Generic
  Imports System.Net
  Imports System.Net.Http
  Imports System.Threading.Tasks
  Imports System.Web.Http
  Imports Microsoft.WindowsAzure.StorageClient

  Public Class FilesController
      Inherits ApiController

      ''' <summary>
      ''' 上傳檔案至Windows Azure Blob儲存體
      ''' </summary>
      Function PostFile() As Task(Of List(Of FileDetails))
          ' 確認MIME為form-data(上傳檔案)
          If Not Request.Content.IsMimeMultipartContent("form-data") Then
              ' 回傳HTTP 415 狀態碼
              Throw New HttpResponseException(HttpStatusCode.UnsupportedMediaType)
          End If

          Dim streamProvider = New AzureBlobStorageMultipartProvider(BlobHelper.GetWebApiContainer())
          ' ReadAsMultipartAsync:http://msdn.microsoft.com/zh-tw/library/system.net.http.httpcontentmultipartextensions(v=vs.108).aspx
          ' 讀取 MIME 多組件訊息的所有主體組件,並產生一組 HttpContent 執行個體,使用 streamProvider 執行個體判斷寫入每一個主體組件的內容。
          Return Request.Content.ReadAsMultipartAsync(Of AzureBlobStorageMultipartProvider)(streamProvider) _
              .ContinueWith(Of List(Of FileDetails))(
                  Function(f)
                      ' 如果Task發生例外就中斷
                      If f.IsFaulted Then
                          Throw f.Exception
                      End If

                      Dim provider As AzureBlobStorageMultipartProvider = f.Result
                      Return provider.Files
                  End Function)
      End Function

      ''' <summary>
      ''' 由Blob容器取得已上傳的檔案清單
      ''' </summary>
      Iterator Function GetFiles() As IEnumerable(Of FileDetails)
          Dim container As CloudBlobContainer = BlobHelper.GetWebApiContainer()
          For Each blob As CloudBlockBlob In container.ListBlobs()
              Yield New FileDetails With {
                  .ContentType = blob.Properties.ContentType,
                  .Location = blob.Uri.AbsoluteUri,
                  .Name = blob.Name,
                  .Size = blob.Properties.Length}
          Next
      End Function
  End Class
  

HTTP POST會對應至PostFile方法,HTTP GET會對應至GetFile方法。PostFile方法讀取上傳的檔案並寫入Blob儲存體之中。GetFile方法會讀取容器內的資訊,產出一分清單。

接下來看是要用Postman或Fiddler進行上傳檔案測試即可。

上傳大檔案的額外設定

ASP.NET預設上傳的檔案大小是 4 MB,如果有上傳大檔案的需求,可以在web.config進行組態設置。

   <httpRuntime targetFramework="4.5" maxRequestLength ="1048576"/>
  

重點在maxRequestLength的設置,例如,上面設置允許上傳 1 GB 大小的檔案。

另外,如果是在IIS,可以設置maxAllowedContentLength,例如,下面在IIS端設置允許 2GB 大小的檔案。

  <system.webServer> 
      <security>
          <requestFiltering>
              <requestLimits maxAllowedContentLength="2147483648" /> 
          </requestFiltering> 
      </security> 
  </system.webServer>   
  

這邊注意一下,maxRequestLength的計算單位是kilobytes,maxAllowedContentLength的計算單位是bytes

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。