1

I'm trying to figure out how to upload an image using ngx-awesome-uploader to azure storage blob from Angular.

I'd like to be able to send it directly to azure storage blob from angular using this library. I've been able to send it to my nodejs backend no problem, but it's been challenging for me to send it directly to blob storage instead. Can anyone provide a working example of how to do this? I appreciate any help!

Choose Simple Demo in stackblitz. Not Advanced Demo

Stackblitz example of ngx awesome uploader

The file is passed to this. (Console output below code)

import { FilePreviewModel } from 'ngx-awesome-uploader';
import { HttpRequest, HttpClient, HttpEvent, HttpEventType } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { FilePickerAdapter } from 'ngx-awesome-uploader';

export class DemoFilePickerAdapter extends FilePickerAdapter {
  constructor(private http: HttpClient) {
    super();
  }
  public uploadFile(fileItem: FilePreviewModel) {
    const form = new FormData();
    form.append('file', fileItem.file);

   console.log("FILE OUTPUT");
    console.log(fileItem.file);


//need to replace everything below with code to add to storage blob

    const api = 'https://demo-file-uploader.free.beeceptor.com';
    const req = new HttpRequest('POST', api, form, {reportProgress: true});
    return this.http.request(req)
    .pipe(
      map( (res: HttpEvent<any>) => {
          if (res.type === HttpEventType.Response) {
          return res.body.id.toString();
        } else if (res.type ===  HttpEventType.UploadProgress) {
            // Compute and show the % done:
            const UploadProgress = +Math.round((100 * res.loaded) / res.total);
            return UploadProgress;
        }
      })
      );
  }

}

the console.output of FILE OUTPUT is

enter image description here

user6680
  • 79
  • 6
  • 34
  • 78
  • https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-javascript-client-libraries#upload-blobs – peinearydevelopment Mar 04 '20 at 19:44
  • This code snippet is confusing to me. Can you show me an example with the stackblitz I linked? – user6680 Mar 04 '20 at 19:54
  • @user6680 - please edit your question to contain any/all relevant code, rather than asking people to click through to a link. Links-to-code are discouraged, since links can rot/die/disappear, then future readers have no reference. It's important to include all specifics in your question, including the area in which you're having the problem, errors, expected vs actual outcome, etc. – David Makogon Mar 04 '20 at 22:18
  • Sorry about that. I added additional information to my question – user6680 Mar 04 '20 at 22:42

1 Answers1

11

According to my test, if you want to upload file to Azure blob, please refer to the following steps

  1. install Azure storage SDk
npm install @azure/storage-blob
  1. Update app.component.html File
<div class="form-group">
  <label for="file">Choose File</label>
  <input type="file"
         id="file"
         (change)="onFileChange($event)">
</div>


  1. Update Environment.ts
export const environment = {
  production: false,
  accountName : "<account name>",
  containerName:"",
   key:""
};
  1. Add the following code in polyfills.ts
(window as any).global = window;
(window as any).process = require( 'process' );
(window as any).Buffer = require( 'buffer' ).Buffer;
  1. Add the following code in app.component.ts
import { Component } from '@angular/core';
import {BlobServiceClient,AnonymousCredential,newPipeline } from '@azure/storage-blob';
import { environment } from './../environments/environment';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'web1';
  currentFile : File =null;
  onFileChange(event) {
    this.currentFile = event.target.files[0];
   console.log(this.currentFile.name)
    console.log(this.currentFile.type)
// generate account sas token
  const accountName =environment.accountName;
  const key=environment.key;
  const start = new Date(new Date().getTime() - (15 * 60 * 1000));
  const end = new Date(new Date().getTime() + (30 * 60 * 1000));
const signedpermissions = 'rwdlac';
  const signedservice = 'b';
  const signedresourcetype = 'sco';
  const signedexpiry = end.toISOString().substring(0, end.toISOString().lastIndexOf('.')) + 'Z';
  const signedProtocol = 'https';
  const signedversion = '2018-03-28';

  const StringToSign =
      accountName+ '\n' +
      signedpermissions + '\n' +
      signedservice + '\n' +
      signedresourcetype + '\n' +
       '\n' +
      signedexpiry + '\n' +
       '\n' +
      signedProtocol + '\n' +
signedversion + '\n';
  const crypto =require('crypto')
   const sig = crypto.createHmac('sha256', Buffer.from(key, 'base64')).update(StringToSign, 'utf8').digest('base64');
  const sasToken =`sv=${(signedversion)}&ss=${(signedservice)}&srt=${(signedresourcetype)}&sp=${(signedpermissions)}&se=${encodeURIComponent(signedexpiry)}&spr=${(signedProtocol)}&sig=${encodeURIComponent(sig)}`;
  const containerName=environment.containerName;
  
            const pipeline =newPipeline (new AnonymousCredential(),{
            retryOptions: { maxTries: 4 }, // Retry options
            userAgentOptions: { userAgentPrefix: "AdvancedSample V1.0.0" }, // Customized telemetry string
            keepAliveOptions: {
                // Keep alive is enabled by default, disable keep alive by setting false
                enable: false
            }
            });
   
            const blobServiceClient =new BlobServiceClient(`https://${accountname}.blob.core.windows.net?${sasToken}`,
                                                             pipeline  )
            const containerClient =blobServiceClient.getContainerClient(containerName)
            if(!containerClient.exists()){
            console.log("the container does not exit")
            await containerClient.create()

            }
            const client = containerClient.getBlockBlobClient(this.currentFile.name)
           const response = await client.uploadBrowserData(this.currentFile,{
                  blockSize: 4 * 1024 * 1024, // 4MB block size
                  concurrency: 20, // 20 concurrency
                  onProgress: (ev) => console.log(ev),
                  blobHTTPHeaders :{blobContentType:this.currentFile.type}
                  })
    console.log(response._response.status)
 }
}

  1. Configure CORS for Azure storage
Allowed origins: *
Allowed verbs: DELETE,GET,HEAD,MERGE,POST,OPTIONS,PUT
Allowed headers: *
Exposed headers: *
Maximum age (seconds): 86400
  1. Test. I upload a pdf file enter image description here enter image description here

Regarding how to configure CORS, please refer to the following steps

  1. Sign in Azure Portal.

  2. Select the Azure account you use

  3. Configure CORS

enter image description here


Update

If you cannot use function createHmac, you can try to use crypto-js. The detailed steps are as below

  1. install sdk
npm install crypto-js --save
npm install @types/crypto-js --save-dev
  1. Update code in the in app.component.ts
...
import * as CryptoJS from 'crypto-js';


...
export class AppComponent {
  title = 'web1';
  currentFile : File =null;
  onFileChange(event) {
    this.currentFile = event.target.files[0];
   console.log(this.currentFile.name)
    console.log(this.currentFile.type)
// generate account sas token
  const accountName =environment.accountName;
  const key=environment.key;
  const start = new Date(new Date().getTime() - (15 * 60 * 1000));
  const end = new Date(new Date().getTime() + (30 * 60 * 1000));
const signedpermissions = 'rwdlac';
  const signedservice = 'b';
  const signedresourcetype = 'sco';
  const signedexpiry = end.toISOString().substring(0, end.toISOString().lastIndexOf('.')) + 'Z';
  const signedProtocol = 'https';
  const signedversion = '2018-03-28';

  const StringToSign =
      accountName+ '\n' +
      signedpermissions + '\n' +
      signedservice + '\n' +
      signedresourcetype + '\n' +
       '\n' +
      signedexpiry + '\n' +
       '\n' +
      signedProtocol + '\n' +
signedversion + '\n';

 var str =CryptoJS.HmacSHA256(StringToSign,CryptoJS.enc.Base64.parse(key));
 var sig = CryptoJS.enc.Base64.stringify(str);
 
 
  const sasToken =`sv=${(signedversion)}&ss=${(signedservice)}&srt=${(signedresourcetype)}&sp=${(signedpermissions)}&se=${encodeURIComponent(signedexpiry)}&spr=${(signedProtocol)}&sig=${encodeURIComponent(sig)}`;
  const containerName=environment.containerName;

            const pipeline =newPipeline (new AnonymousCredential(),{
            retryOptions: { maxTries: 4 }, // Retry options
            userAgentOptions: { userAgentPrefix: "AdvancedSample V1.0.0" }, // Customized telemetry string
            keepAliveOptions: {
                // Keep alive is enabled by default, disable keep alive by setting false
                enable: false
            }
            });

            const blobServiceClient =new BlobServiceClient(`https://${accountname}.blob.core.windows.net?${sasToken}`,
                                                             pipeline  )
            const containerClient =blobServiceClient.getContainerClient(containerName)
            if(!containerClient.exists()){
            console.log("the container does not exit")
            await containerClient.create()

            }
            const client = containerClient.getBlockBlobClient(this.currentFile.name)
           const response = await client.uploadBrowserData(this.currentFile,{
                  blockSize: 4 * 1024 * 1024, // 4MB block size
                  concurrency: 20, // 20 concurrency
                  onProgress: (ev) => console.log(ev),
                  blobHTTPHeaders :{blobContentType:this.currentFile.type}
                  })
    console.log(response._response.status)
 }
}
Community
  • 1
  • 1
Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • I've integrated you're code and I've gotten rid of ngx-awesome-uploader due to how it's designed. Getting rid of this library has made things easier for me with handling the file. I'm now using a different image uploader/cropper that when I hit confirm image, it stores the image as a base64 type. I've added your code, but I'm getting a CORS error. I know you mention CORS in your answer, but I don't understand where I'm supposed to add CORS in angular. ```Reason: CORS header ‘Access-Control-Allow-Origin’ missing)``` – user6680 Mar 05 '20 at 20:23
  • Makes sense. Thanks for clarifying. I have one other issue that that I've been trying to solve. CORS was the issue at first, but now it's saying ```crypto.createHmac is not a function``` Didn't know you could reference nodejs module ```const crypto = require('crypto')``` I tried installing npm i crypto --save, but it the only thing in there is a package.json file and readme file. package.json file : https://pastebin.com/xrXPeFTe I tried this : https://stackoverflow.com/a/43353846/4350389 , but it gives me same error even though it recognizes function: https://postimg.cc/zytTWn97 Any ideas? – user6680 Mar 06 '20 at 01:49
  • @user6680 Could you please tell me your angular version? – Jim Xu Mar 06 '20 at 02:05
  • it's Angular: 8.2.8 – user6680 Mar 06 '20 at 02:07
  • @user6680 Could you please try to use ```import {createHmac} from 'crypto'```? – Jim Xu Mar 06 '20 at 02:30
  • I imported it but ```'createHmac' is declared but its value is never read``` if I hover over declaration. – user6680 Mar 06 '20 at 02:38
  • @user6680 Please try to run ```npm install crypto``` then use ```import {createHmac} from 'crypto'``` https://i.stack.imgur.com/mOyzh.png – Jim Xu Mar 06 '20 at 03:03
  • I did that and it can see the createHmac function https://postimg.cc/9rw7zJTY but the crypto module is using the require library https://postimg.cc/vDc1S3Pw same error – user6680 Mar 06 '20 at 03:21
  • Please try to remove these two lines code and directly use the ```createHmac``` function https://i.stack.imgur.com/VtekH.png and https://i.stack.imgur.com/RwPnZ.png – Jim Xu Mar 06 '20 at 03:42
  • Gotcha. I'm not getting that error anymore, but it's now saying ```"Uncaught (in promise): TypeError: Object(...) is not a function``` The error is being thrown by the ```const sig``` line. I added a ```return;``` before it no error. After the ```const sig``` line, the error comes up. Here's my updated code: https://pastebin.com/7N1Uf2Mr – user6680 Mar 06 '20 at 03:58
  • @user6680 Could you please provide the detailed error message – Jim Xu Mar 06 '20 at 04:03
  • @user6680 According to my test, we also need to use ```var crypto = require('crypto') crypto.createHmac()```. – Jim Xu Mar 06 '20 at 05:16
  • it is a built-in Node module. – Jim Xu Mar 06 '20 at 05:59
  • If it's a built in module, wouldn't this work? https://postimg.cc/p98jk08H @types/node? Also in your answer you said to put the code in app.component.ts. I put it in a component I created, but I doubt that matters, right? Also what if we tried using ```crypto-js```? I see it doesn't have the exact ```createHmac()``` function, but it looks like it has something similar? I've tried all the answers from this post: https://stackoverflow.com/questions/43353718/how-to-use-crypto-module-in-angular2/43353846#43353846 , but none of them work so I'm not really sure at this point – user6680 Mar 06 '20 at 14:52
  • Hey Jim. Letting you know I just made a separate post for the ```createHmac is not a function``` issue https://stackoverflow.com/questions/60578019/crypto-createhmac-is-not-a-function-angular – user6680 Mar 07 '20 at 13:31
  • It worked! Thanks so much. I have one other quick question though. I added the file successfully, but when I try to open it, it says it's doesn't support file type when trying to view it in windows Photos viewer. Pretty sure it's because of content-type. https://postimg.cc/NKwQ2mbf I changed this line to reflect output in storage btw ```const client = containerClient.getBlockBlobClient("MYFILE.png")``` Any suggestions? – user6680 Mar 09 '20 at 14:37
  • I tried ```blobHTTPHeaders: { blobContentType: "image/png" }``` or ```blobHTTPHeaders: { blobContentType: "image/jpg" }``` but when I try to open/donwload it from storage explorer, it says https://postimg.cc/gXf34Jts – user6680 Mar 09 '20 at 18:45
  • @user6680 According to my test, it is ok for me. If I set content-type, I can see the picture on Azure Portal : https://i.stack.imgur.com/WWz6U.png and https://i.stack.imgur.com/eZhpx.png and https://i.stack.imgur.com/YR3DZ.png – Jim Xu Mar 10 '20 at 03:22
  • You're file format appears to be different than mine. Mine is base64 https://postimg.cc/hXXyhL1X. I did the first example in this answer https://stackoverflow.com/a/38936042/4350389 and converted it to image. Uploaded to blob and now I can see the image. Thanks for everything! – user6680 Mar 10 '20 at 14:57
  • I have one recommendation though as an edit for your code for future readers. Since it's using async/await, currently if the container doesn't exist, it will not hit the if condition and create the container. I got that part working by doing the following: https://pastebin.com/2c3NEhQi – user6680 Mar 10 '20 at 20:08
  • Great demo. Question, `var str =CryptoJS.HmacSHA256(StringToSign,CryptoJS.enc.Base64.parse(key));` already sign `str`, why continue with `var sig = CryptoJS.enc.Base64.stringify(str);`? Do we verify it for every request? – Jeb50 Feb 09 '22 at 19:21