July 22, 2024

Managing Azure Blobs with .Net and React – Ultimate Guide

In this article, we will look into fetching, showing, and deleting Blobs from Azure Blob Storage Account. This is a continuation of the previous article, which you can find here.

Pre-checks

In this article we will consider most basic scenario in which we will show Blobs in the web app using Url. In order to enable this your Storage Account should enable Blob anonymous access. You can do that in Storage Account > Settings > Configuration

Get list of all Blobs

First let’s take a look into the list of all the Blobs in your container. First step is creating a separate method for authorising your calls. We made a slight improvement since last time when we used ConnectionString directly from the code 🤢.

    private static BlobContainerClient BlobContainerClient()
    {
        var blobServiceClient = new BlobServiceClient(
            new Uri("https://<STORAGE ACCOUNT NAME>.blob.core.windows.net"),
            new DefaultAzureCredential());
        var containerName = "<CONTAINER NAME>";
        var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
        return blobContainerClient;
        
    }

Next step is preparing your controller. In a simple version it can look something like this:

    [HttpGet]
    public IEnumerable<BlobView> GetAllBlobs()
    {
        return GetAllBlobsFromContainer();
    }

    private IEnumerable<BlobView> GetAllBlobsFromContainer()
    {
        var blobContainerClient = BlobContainerClient();
        var blobs = blobContainerClient.GetBlobs();
        var blobsList = blobs.ToList();
        var blobView = new List<BlobView>();
        foreach (var blb in blobsList.Where(x => x.Deleted == false))
        {
            BlobClient blobClient = blobContainerClient.GetBlobClient(blb.Name);
            blobView.Add(new BlobView()
            {
                Name = blb.Name,
                Url = blobClient.Uri.AbsoluteUri
            });
        }
        return blobView;
    }

In a response from GetBlobs() we are getting type Pageable<BlobItem>. Trick here is, that BlobItem type doesn’t contain information about Blob’s Uri, which we need in order to show the picture in the frontend. To get that we need read it from BlobClient, hence the need for iteration. In the same loop we are also mapping the result to much simpler view object, that contains only two properties – Name and Url.

Delete a Blob

You can delete a Blob by name (name is a unique identifier for the Blob). I*n order to do that we can use DeleteAsync() method in BlobClient.

    [HttpDelete("{name}")]
    public async Task DeleteBlob(string name)
    {
        await DeleteBlobByName(name);
    }

    private async Task DeleteBlobByName(string name)
    {
        var blobContainerClient = BlobContainerClient();
        var blobClient = blobContainerClient.GetBlobClient(name);
        await blobClient.DeleteAsync();
    }

Frontend

Let’s take a look into frontend React application, and extend what we created previously. Our end-goal is to show list of names of all Blobs in the container, when name is clicked we would like to see the picture and be able to delete it.

First step is making sure we fetch list of all available blobs on first page load. We can do this with useEffect hook. It can be as simple as presented below.

    const [blobs, setBlobs] = useState<BlobView[]>();

    useEffect(() => {
        getAllBlobs();
    }, [blobs && blobs!.length > 0 ])

    const getAllBlobs = () => {
        axios.get("/api/blob")
            .then((res) => {
                setBlobs(res.data);
        });
    }

As we need to refetch blobs on every upload and/or delete action we moved fetching logic to a separate method, for that we need a variable to hold all the values – for that we use stateful values and useState hook. We would like to show selected image and be able to delete it. This can look something like:

    const [selectedImage, setSelectedImage] = useState<BlobView | null>();

    const showBlob = (blob: BlobView) => {
        setSelectedImage(blob)
    }

    const handleDelete = (name: string) => {
        axios.delete(`/api/blob/${name}`)
            .then(() => {
                setSelectedImage(null);
                getAllBlobs();
            })
    }

Finally, let’s modify what we’ve done previously in return statement.

  return (
      <>
          <div className="App">
              <header className="App-header">
                  <input type="file" onChange={handleFileChange}/>
                  <div>{file && `${file.name} - ${file.type}`}</div>
                  <button disabled={!file} onClick={handleUpload}>Upload</button>
                  <ul>
                      {blobs && blobs.map((blob) =>
                          <li key={blob.Name}
                              onClick={() => showBlob(blob)}
                              className="blob">{blob.Name}</li>)}
                  </ul>
                  {
                      selectedImage &&
                      <>
                          <img width={100} height={100} src={selectedImage.Url}/>
                          <button onClick={() => handleDelete(selectedImage.Name)}>Delete</button>
                      </>
                  }
              </header>
          </div>
      </>
  );

Final result should be something like this.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.