CRUD Operations With Azure Blob Storage, .NET 6, And Angular 14

Introduction 

Microsoft has introduced a new library, Azure.Storage.Blobs for handling the Blob operations. I have already written an article about Blob operations a few years ago on C# Corner.  

Above article discussed based on an application which was built with .NET Core 2.2 and Angular 8. That application was referring to the library WindowsAzure.Storage which was now marked as obsolete by Microsoft.  

Since both .NET Core and Angular versions are changed regularly and recently came with the latest versions 6 and 14 respectively with drastic changes, I have decided to create a new article which will use .NET 6 and Angular 16. I will explain all the steps one by one.  

We can create an Azure storage account first.  

Create Azure storage account on Azure Portal 

We can log into Azure portal and create a new Azure Blob storage. 

We can choose any existing resource group or create a new resource group. We must choose a region and supply a valid account name. 

We can click the Create button to create our new Blob storage. The new resource will be deployed within a few moments. 

Currently, there are four types of services available in Blob storage. 

Blob service, File service, Queue service and Table service are the services. In this article, we will be discussing about Blob service. 

Create ASP.NET Core 6.0 Web API using Visual Studio 2022 

We can create our Web API project using Visual Studio 2022. We must choose the ASP.NET Core template and .NET 6.0 framework. 

Our new project will be created with default Swagger documentation soon. 

We can install the Azure.Storage.Blobs library using NuGet package manager. 

We can create a configuration inside the appsettings.json file and add our Azure Storage account connection string and container name.  

You can get the storage connection from the Access keys section of storage account. Click the Show keys button to get the connection string. 

Please note that, we have not yet created a Container inside the Blob storage. Our application will automatically create a container if it does not exist. We will write the code for that later.  

We can put the connection string and container name inside the appsettings.json file.  

appsettings.json 

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MyConfig": {
    "StorageConnection": "DefaultEndpointsProtocol=https;AccountName=sarathblobstorage;AccountKey=IMGCgU7hTXUO9hAzRd9YZXCfGy/QWFX048+301cNpxVOGkoRV+9+Lad/tJ5XjDfR2pUALEXbSk2J+AStloNliQ==;EndpointSuffix=core.windows.net",
    "ContainerName": "sarathcontainer"
  }
}

We need one interface and service class to provide CRUD operations. This service class can be used from our API controller.  

We can create our IBlobStorage interface with the method definitions below. 

IBlobStorage.cs 

namespace AzureBlobDotNET6.Helpers
{
    public interface IBlobStorage
    {
        public Task<List<string>> GetAllDocuments(string connectionString, string containerName);
        Task UploadDocument(string connectionString, string containerName, string fileName, Stream fileContent);
        Task<Stream> GetDocument(string connectionString, string containerName, string fileName);
        Task<bool> DeleteDocument(string connectionString, string containerName, string fileName);
    }
}

We can create BlobStorage service class and implement above IBlobStorage interface. 

BlobStorage.cs 

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

namespace AzureBlobDotNET6.Helpers
{
    public class BlobStorage : IBlobStorage
    {
        public async Task<List<string>> GetAllDocuments(string connectionString, string containerName)
        {
            var container = BlobExtensions.GetContainer(connectionString, containerName);

            if (!await container.ExistsAsync())
            {
                return new List<string>();
            }

            List<string> blobs = new();

            await foreach (BlobItem blobItem in container.GetBlobsAsync())
            {
                blobs.Add(blobItem.Name);
            }
            return blobs;
        }

        public async Task UploadDocument(string connectionString, string containerName, string fileName, Stream fileContent)
        {
            var container = BlobExtensions.GetContainer(connectionString, containerName);
            if (!await container.ExistsAsync())
            {
                BlobServiceClient blobServiceClient = new(connectionString);
                await blobServiceClient.CreateBlobContainerAsync(containerName);
                container = blobServiceClient.GetBlobContainerClient(containerName);
            }

            var bobclient = container.GetBlobClient(fileName);
            if (!bobclient.Exists())
            {
                fileContent.Position = 0;
                await container.UploadBlobAsync(fileName, fileContent);
            }
            else
            {
                fileContent.Position = 0;
                await bobclient.UploadAsync(fileContent, overwrite: true);
            }
        }
        public async Task<Stream> GetDocument(string connectionString, string containerName, string fileName)
        {
            var container = BlobExtensions.GetContainer(connectionString, containerName);
            if (await container.ExistsAsync())
            {
                var blobClient = container.GetBlobClient(fileName);
                if (blobClient.Exists())
                {
                    var content = await blobClient.DownloadStreamingAsync();
                    return content.Value.Content;
                }
                else
                {
                    throw new FileNotFoundException();
                }
            }
            else
            {
                throw new FileNotFoundException();
            }

        }

        public async Task<bool> DeleteDocument(string connectionString, string containerName, string fileName)
        {
            var container = BlobExtensions.GetContainer(connectionString, containerName);
            if (!await container.ExistsAsync())
            {
                return false;
            }

            var blobClient = container.GetBlobClient(fileName);

            if (await blobClient.ExistsAsync())
            {
                await blobClient.DeleteIfExistsAsync();
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    public static class BlobExtensions
    {
        public static BlobContainerClient GetContainer(string connectionString, string containerName)
        {
            BlobServiceClient blobServiceClient = new(connectionString);
            return blobServiceClient.GetBlobContainerClient(containerName);
        }
    }
}

We need a minor change in the Program.cs file to enable CORS (Cross Origin Resource Sharing). So that we can send Http requests from Angular application without any issues. We can also inject our BlobStorage service class and interface using dependency injection.  

Program.cs 

using AzureBlobDotNET6.Helpers;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddScoped<IBlobStorage, BlobStorage>();

var MyAllowedOrigins = "_myAllowedOrigins";
builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowedOrigins,
                          builder =>
                          {
                              builder.WithOrigins("http://localhost:4200")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.UseCors(MyAllowedOrigins);

app.Run();

We can create our API controller AzureCRUDController and add the code below. 

AzureCRUDController.cs 

using AzureBlobDotNET6.Helpers;
using Microsoft.AspNetCore.Mvc;

namespace AzureBlobDotNET6.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AzureCRUDController : ControllerBase
    {
        private readonly IBlobStorage _storage;
        private readonly string _connectionString;
        private readonly string _container;

        public AzureCRUDController(IBlobStorage storage, IConfiguration iConfig)
        {
            _storage = storage;
            _connectionString = iConfig.GetValue<string>("MyConfig:StorageConnection");
            _container = iConfig.GetValue<string>("MyConfig:ContainerName");
        }

        [HttpGet("ListFiles")]
        public async Task<List<string>> ListFiles()
        {
            return await _storage.GetAllDocuments(_connectionString, _container);
        }

        [Route("InsertFile")]
        [HttpPost]
        public async Task<bool> InsertFile([FromForm] IFormFile asset)
        {
            if (asset != null)
            {
                Stream stream = asset.OpenReadStream();
                await _storage.UploadDocument(_connectionString, _container, asset.FileName, stream);
                return true;
            }

            return false;
        }

        [HttpGet("DownloadFile/{fileName}")]
        public async Task<IActionResult> DownloadFile(string fileName)
        {
            var content = await _storage.GetDocument(_connectionString, _container, fileName);
            return File(content, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
        }

        [Route("DeleteFile/{fileName}")]
        [HttpGet]
        public async Task<bool> DeleteFile(string fileName)
        {
            return await _storage.DeleteDocument(_connectionString, _container, fileName);
        }

    }
}

We have completed the API coding. Now we can create the Angular 14 project. 

Create Angular 14 project using Angular CLI 

We can create a new Angular project using CLI. 

We need “bootstrap”, “font-awesome", and “file-saver” libraries for this project. We can install these libraries using npm command. 

npm install bootstrap font-awesome file-saver 

To use file-saver library, typescript definitions can be installed additionally via: 

npm install @types/file-saver 

We can import “bootstrap” and “font-awesome” libraries inside the style.css class in the root folder of “src” folder. This way, we can use these libraries in the entire project without further references. 

styles.css 

@import "~bootstrap/dist/css/bootstrap.css";  
@import "~font-awesome/css/font-awesome.css";  

Let us add “HttpClientModule” and “FormsModule” in app.module.ts file. We will use both these modules in our project. 

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,  
    FormsModule  
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We can add a baseUrl variable inside the environment.ts file. 

environment.ts 

export const environment = {
  production: false,
  baseUrl: 'http://localhost:5000/'
};

We can change the app.component.ts component file with the code below. 

app.component.ts 

import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import * as saveAs from 'file-saver';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private http: HttpClient) { }
  files: string[] = [];
  fileToUpload!: FormData;
  showLoader!: boolean;
  @ViewChild("fileUpload", { static: false }) fileUpload!: ElementRef;

  private url = environment.baseUrl + 'api/azurecrud';

  ngOnInit(): void {
    this.showBlobs();
  }

  showBlobs() {
    this.showLoader = true;
    this.http.get<string[]>(this.url + '/listfiles')
      .subscribe({
        next: (result) => {
          this.files = result
        },
        error: (err) => {
          console.error(err);
        },
        complete: () => {
          this.showLoader = false;
        }
      });
  }

  onClick() {
    let fileUpload = this.fileUpload.nativeElement;
    fileUpload.onchange = () => {
      this.showLoader = true;
      const file = fileUpload.files[0];
      let formData: FormData = new FormData();
      formData.append("asset", file, file.name);
      this.http.post(this.url + '/insertfile', formData)
        .subscribe({
          next: (response: any) => {
            if (response == true) {
              this.showBlobs();
            }
          },
          error: (err) => {
            console.error(err);
            this.showLoader = false;
          },
          complete: () => {
          }
        });
    };
    fileUpload.click();
  }

  downloadFile(fileName: string) {
    this.showLoader = true;
    return this.http.get(this.url + '/downloadfile/' + fileName, { responseType: "blob" })
      .subscribe({
        next: (result: any) => {
          if (result.type != 'text/plain') {
            var blob = new Blob([result]);
            let file = fileName;
            saveAs(blob, file);
          }
          else {
            alert('File not found in Blob!');
          }
        },
        error: (err) => {
          console.error(err);
        },
        complete: () => {
          this.showLoader = false;
        }
      });
  }

  deleteFile(fileName: string) {
    var del = confirm('Are you sure want to delete this file');
    if (!del) return;
    this.showLoader = true;
    this.http.get(this.url + '/deletefile/' + fileName)
      .subscribe({
        next: (result: any) => {
          if (result != null) {
            this.showBlobs();
          }
        },
        error: (err) => {
          console.error(err);
          this.showLoader = false;
        },
        complete: () => {
        }
      });
  }
}

We have added all the logic for file operations with Blob Storage in this component. 

Modify the corresponding HTML and CSS files for this component as well. 

app.component.html 

<div style="text-align:center; margin-top: 20px;">
  <h4>
    CRUD operations with Azure Blob Storage, .NET 6 and Angular 14
  </h4>
</div>

<div class="blobstorage-section">
  <i class="fa fa-plus fa-2x" style="cursor: pointer; color: darkslateblue;" (click)="onClick()"></i>&nbsp; Add new files to Blob
  <input type="file" #fileUpload id="fileUpload" name="fileUpload" style="display:none;" (click)="fileUpload.value = ''"/>
  <table style="margin-top: 20px;">
    <tr>
      <th class="column1">Uploaded Files</th>
      <th class="column2" style="text-align:center;">Download</th>
      <th class="column3" style="text-align:center;">Delete</th>
    </tr>
    <tr *ngFor="let file of files">
      <td class="column1">{{file}}</td>
      <td class="column2" style="text-align:center;cursor: pointer;" (click)="downloadFile(file)"><i class="fa fa-download"></i></td>
      <td class="column3" style="text-align:center;" (click)="deleteFile(file)">
        <img alt="Group Audit" src="../assets/icon-download.png" />
      </td>
    </tr>
  </table>
</div>
<div class="file-loader" *ngIf="showLoader">
  <div class="upload-loader">
    <div class="loader"></div>
  </div>
</div>


app.component.css 

/* Spin Start*/

.file-loader {
    background-color:
        rgba(0, 0, 0, .5);
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 100000 !important;
}

.upload-loader {
    position: absolute;
    width: 60px;
    height: 60px;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}

.upload-loader .loader {
    border: 5px solid #f3f3f3 !important;
    border-radius: 50%;
    border-top: 5px solid #005eb8 !important;
    width: 100% !important;
    height: 100% !important;
    -webkit-animation: spin 2s linear infinite;
    animation: spin 2s linear infinite;
}

@-webkit-keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
    }

    100% {
        -webkit-transform: rotate(360deg);
    }
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

/* Spin End*/

.blobstorage-section {
    font-family: Calibri;
    box-shadow: 0 1px 4px 0 #9b9b9b;
    background-color: #ffffff;
    margin: auto;
    margin-top: 50px;
    padding: 30px;
    width: 50%;
}

.column1 {
    width: 450px;
}

.column2 {
    width: 100px;
}

.column3 {
    width: 100px;
}

.blobstorage-section th {
    font-family: Calibri;
    font-size: 14px;
    font-weight: bold;
    font-style: normal;
    font-stretch: normal;
    line-height: 1.57;
    letter-spacing: normal;
    color: #333333;
    border-right: 1px solid #d7d7d7;
    border-bottom: 1px solid #d7d7d7;
}

.blobstorage-section th i {
    font-family: Calibri;
    font-size: 16px;
}

.blobstorage-section tbody tr td {
    border-right: 1px solid #d7d7d7;
    border-bottom: 1px solid #d7d7d7;
}

.blobstorage-section tbody tr td a {
    font-family: Calibri;
    font-size: 15px;
    font-weight: normal;
    font-style: normal;
    font-stretch: normal;
    line-height: 1.2;
    letter-spacing: normal;
    color: #0091da;
    text-decoration: underline;
}

.blobstorage-section tr td img:hover {
    cursor: pointer;
}

We have completed the Angular project as well. We can run both ASP.NET Core project and Angular project, now. 

We can add new files by clicking + button on the screen. 

You can click the Download and Delete button to perform respective actions as well. 

Conclusion 

In this post, we have seen how to upload, list, download and remove files from Azure Blob storage using ASP.NET 6.0 API project and we have consumed these services in an Angular 14 client project. Hopefully, I have covered all the details needed for CRUD operations related to Azure Blob storage. If you are interested, you can download the source code and check from your side.