Recently I am working on an internal OA organization for a minor visitor. One of their business organization requirement is for the users to upload attachments when creating a ticket. Azure Blob Storage is ideal for this scenario. Nonetheless, the attachments are sized from a few KB to hundreds of MB, and so it is necessary to show a progress bar when user upload the files. Let's see how tin can we easily do information technology with Azure.

The Design


The OA organisation is using ASP.Cyberspace Core as it'due south backend API. And then basically I can come upward with two designs for uploading files to Azure Hulk Storage.

  1. Create an API that accept files from web front-end and then upload to Azure.
  2. Create an API return simply SAS token for Azure Storage, web front-end then employ this token to upload files directly to Azure without calling our own API.

Option 1 is too expensive because this require API server to allow large request body, and I take to write a lot of complicated lawmaking to enable reporting progress to client, non to mention all the error scenarios.

Since Azure Storage already got JavaScript API that can study upload progress, I decided to go with choice ii.

What need to be prepared in Azure


Container

Create a container for storing uploaded files. e.g. "attachments".

Connectedness String

Re-create the connection cord from the storage account for later on apply.

CORS

Permit CORS on Blob service for your domain. For demo, I just enable all origins and all methods. Please practice non practise this in product. To upload files, only HTTP PUT is needed.

Create API to render SAS Token


Install latest Azure.Storage.Blobs package.

          <PackageReference Include="Azure.Storage.Blobs" Version="12.ix.one" />        

Add Azure Storage settings to appsettings.json

          "AppSettings": {   "AzureStorage": {     "ConnectionString": "<connexion string>",     "ContainerName": "attachments",     "TokenExpirationMinutes": 20   } }        

Delight adjust TokenExpirationMinutes equally yous need.

Create controller and action

          [ApiController] [Route("[controller]")] public form AttachmentController : ControllerBase {     private readonly IConfiguration _configuration;      public AttachmentController(IConfiguration configuration)     {         _configuration = configuration;     }      [HttpGet("token")]     [ProducesResponseType(StatusCodes.Status409Conflict)]     [ProducesResponseType(typeof(AzureStorageSASResult), StatusCodes.Status200OK)]     public IActionResult SASToken()     {         var azureStorageConfig = _configuration.GetSection("AppSettings:AzureStorage").Go<AzureStorageConfig>();         BlobContainerClient container = new(azureStorageConfig.ConnectionString, azureStorageConfig.ContainerName);          if (!container.CanGenerateSasUri) render Disharmonize("The container can't generate SAS URI");          var sasBuilder = new BlobSasBuilder         {             BlobContainerName = container.Proper noun,             Resource = "c",             ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(azureStorageConfig.TokenExpirationMinutes)         };          sasBuilder.SetPermissions(BlobContainerSasPermissions.All);          var sasUri = container.GenerateSasUri(sasBuilder);          var issue = new AzureStorageSASResult         {             AccountName = container.AccountName,             AccountUrl = $"{container.Uri.Scheme}://{container.Uri.Host}",             ContainerName = container.Name,             ContainerUri = container.Uri,             SASUri = sasUri,             SASToken = sasUri.Query.TrimStart('?'),             SASPermission = sasBuilder.Permissions,             SASExpire = sasBuilder.ExpiresOn         };          return Ok(issue);     } }  public grade AzureStorageConfig {     public string ConnectionString { get; prepare; }     public cord ContainerName { go; prepare; }     public int TokenExpirationMinutes { get; set; } }  public class AzureStorageSASResult {     public string AccountName { get; fix; }     public string AccountUrl { get; set; }     public Uri ContainerUri { get; set up; }     public string ContainerName { become; set; }     public Uri SASUri { get; set; }     public string SASToken { get; prepare; }     public string SASPermission { become; prepare; }     public DateTimeOffset SASExpire { become; set; } }        

Discover the SAS Permissions are set to All for demo here, in production, please set to merely the permissions you need for security reasons.

          sasBuilder.SetPermissions(BlobContainerSasPermissions.All);        

A correct API response will look like this

          {   "accountName": "cinderellastorage",   "accountUrl": "https://cinderellastorage.hulk.core.windows.net",   "containerUri": "https://cinderellastorage.blob.cadre.windows.net/attachments",   "containerName": "attachments",   "sasUri": "https://cinderellastorage.blob.cadre.windows.internet/attachments?sv=2020-08-04&se=2021-07-07T05%3A37%3A07Z&sr=c&sp=racwdxlt&sig=MxCEEtT%2FGVbjksDzgzVgvRqucQfjKd4JOp6zsId0h5c%3D",   "sasToken": "sv=2020-08-04&se=2021-07-07T05%3A37%3A07Z&sr=c&sp=racwdxlt&sig=MxCEEtT%2FGVbjksDzgzVgvRqucQfjKd4JOp6zsId0h5c%3D",   "sasPermission": "racwdxlt",   "sasExpire": "2021-07-07T05:37:07.6443444+00:00" }        

Web Forepart-End


I am not a pro front-end programmer. So I used code from this post with a piffling modification. The azure-storage.blob.min.js library tin be downloaded from https://aka.ms/downloadazurestoragejs

          <!DOCTYPE html> <html lang="en">  <head>     <meta charset="UTF-viii">     <meta http-equiv="X-UA-Compatible" content="IE=border">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <link href="https://cdn.jsdelivr.net/npm/bootstrap@five.0.2/dist/css/bootstrap.min.css" rel="stylesheet"         integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">     <title>Upload</title>     <script src="azure-storage.blob.min.js"></script>     <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-three.6.0.min.js"></script>     <script type="text/javascript">         $(document).on('change', ':file', part () {             var input = $(this)             var label = $('#BrowseInput').val(input.val().replace(/\\/g, '/').supervene upon(/.*\//, ''));         });     </script>     <script blazon="text/javascript">         function displayProcess(process) {             document.getElementById("uploadProgressBar").mode.width = process + '%';             document.getElementById("uploadProgressBar").innerHTML = process + '%';         }          function uploadBlob() {             displayProcess(0);             certificate.getElementById("uploadProgressBarContainer").classList.remove('hidden');              // TODO: Call API to get URL, SAS Token, Container proper name             var blobUri = 'https://cinderellastorage.blob.core.windows.net';             var containerName = 'attachments';             var sasToken = 'sv=2020-08-04&se=2021-07-07T05%3A40%3A03Z&sr=c&sp=racwdxlt&sig=NKDKAdQESM03GxCfFs2FLHHYLWJzqRYy4LKdyxTcVx8%3D';              var blobService = AzureStorage.Blob.createBlobServiceWithSas(blobUri, sasToken);              var file = $('#FileInput').get(0).files[0];              var customBlockSize = file.size > 1024 * 1024 * 32 ? 1024 * 1024 * 4 : 1024 * 512;             blobService.singleBlobPutThresholdInBytes = customBlockSize;              var finishedOrError = false;             var speedSummary = blobService.createBlockBlobFromBrowserFile(containerName, file.name, file, { blockSize: customBlockSize }, office (error, event, response) {                 finishedOrError = true;                 if (error) {                     alert('Error');                 } else {                     displayProcess(100);                 }             });              part refreshProgress() {                 setTimeout(function () {                     if (!finishedOrError) {                         var process = speedSummary.getCompletePercent();                         displayProcess(procedure);                         refreshProgress();                     }                 }, 200);             }              refreshProgress();         }     </script> </caput>  <body>     <div course="modal-dialog">         <div class="modal-content">             <class asp-controller="Home" asp-action="UploadSmallFile" enctype="multipart/form-data" id="BlobUploadForm"                 method="post" form="form-characterization-left" role="course">                 <div class="modal-body">                     <div class="class-grouping">                         <div class="input-group">                             <label class="input-group-btn">                                 <span course="btn btn-primary">                                     Browse… <input type="file" fashion="brandish: none;" name="file" id="FileInput">                                 </span>                             </characterization>                             <input type="text" class="course-control" readonly="" id="BrowseInput">                         </div>                     </div>                     <div grade="grade-group">                         <div grade="input-grouping">                             <button blazon="push button" value="Upload to Blob" class="btn btn-success" id="UploadBlob"                                 onclick="uploadBlob()">Upload to Blob</push button>                         </div>                     </div>                     <div class="form-group hidden" id="uploadProgressBarContainer">                         Uploading...                         <div grade="progress">                             <div class="progress-bar" role="progressbar" id="uploadProgressBar" aria-valuenow="lx"                                 aria-valuemin="0" aria-valuemax="100" manner="width: 0%;">                                 0%                             </div>                         </div>                     </div>                 </div>             </grade>         </div>     </div>     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.ii/dist/js/bootstrap.bundle.min.js"         integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"         crossorigin="anonymous"></script> </body> </html>        

The basic idea in this code is to break big files to trunks and upload them piece by piece. Azure storage SDK will handle the upload and report the progress.

Finally, I tin can verify the file has been uploaded to Azure Storage.