S3 File Operations using Blazor WASM
Tags: dotnet, webassembly, aws, blazor
Published: 2023-03-21 02:11:45

In the previous chapter, we created three APIs that will list all the files available in the S3 bucket, download them, and upload a file to the S3 bucket.

In this chapter, we are going to create the Blazor web assembly front-end application that will provide functionalities to the users to see all the files from the bucket, download them and upload a new file if wanted. We are going to use the APIs we created in the previous chapter as the intermediate service that handles the user request.

Find the complete source code in my GitHub repository below.
> https://github.com/sprabha1990/S3FileOperations.Blazor.NET7

We will use the empty Blazor WASM project that was already in the previous chapter.

Let's open the "S3FileOperations.Blazor" project inside the "S3FileOperations.NET7.sln" in VS2022. As a first step, we are going to incorporate bootstrap v5 into the project. 

Add the following code inside the "__index.html__" available inside "__wwwroot__" folder.

```
<head>
    ...

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">

    ...
</head>
<body>
    ...
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
```
If you have close look at the `<head>` tag, I'm adding the script to use bootstrap icons. If you want to use alternative icons, you can add/replace their script here.

Now, we are going to add an entity class named `Files.cs`. This holds the public properties of the file information that will be shown in the UI. 
Let's add the below properties into the `Files` class.

```
public class Files
    {
        public string ETag { get; set; } = string.Empty;
        public string Key { get; set; } = string.Empty;
        public string BucketName { get; set; } = string.Empty;
        public long Size { get; set; } = 0;
    }
```
Let's add the UI HTML code in the `Index.razor` page available inside the Pages folder.

Workflow is that,

1. We are going to add an HTML table with 4 columns that will show the "eTag, fileName, bucketName, and size of the files retrieved from the S3. 
2. We are going to add a download button adjacent to each row of the table to help users download the file easily.
3. We are going to add a File upload option for the user so that the user can upload any file.


Add the below code in the `Index.razor` file.

```
@page "/"
@using S3FileOperations.Blazor.Entity;
@inject HttpClient _httpClient;
@using Microsoft.AspNetCore.Components.Forms;
@using System.Net.Http.Headers;

<table class="table table-striped mt-5">
     <thead class="table-dark">
     <tr>
          <th scope="col">ETAG</th>
          <th scope="col">FileName</th>
          <th scope="col">BucketName</th>
          <th scope="col">Size (In Bytes)</th>
          <th scope="col"></th>
    </tr>
  </thead>
    <tbody class="table-light">
        @foreach(var file in FilesInS3)
        {
            <tr>
                <th scope="col">@file.ETag</th>
                <th scope="col">@file.Key</th>
                <th scope="col">@file.BucketName</th>
                <th scope="col">@file.Size</th>
                <th scope="col"><a href="**https://localhost:7097**/
api/S3/download?Key=@file.Key" class="link-success">Download</a><i class="bi bi-download ms-3 d-inline-flex align-Items-center text-danger"></i></th>
            </tr>
        }
    </tbody>
</table>

<div class="card d-inline-flex flex-row bd-highlight ms-2 mt-2 w-50 p-3">
    <div class="card-body p-2 bd-highlight">
        <InputFile OnChange="(e) => _currentFile = e.File" class="btn-light mb-2 w-100" />
        @if (_currentFile != null)
        {
            <div class="d-grid gap-3 ">
                <div class="p-2 bg-light border mt-1"><p>Name: @_currentFile.Name</p></div>
                <div class="p-2 bg-light border">Size in bytes: @_currentFile.Size</div>
                <div class="p-2 bg-light border">Last modified date: @_currentFile.LastModified.ToString()</div>
                <div class="p-2 bg-light border">Content type (not always supplied by the browser): @_currentFile.ContentType</div>
            </div>
        }
        <button type="button" class="btn btn-primary mt-3" @onclick="UploadFileAsync">Upload File <i class="bi bi-upload ms-2 d-inline-flex"></i></button>
    </div>
</div>

@code {
    public List<Files> FilesInS3 { get; set; } = new();
    public IBrowserFile? _currentFile = null;

    protected override async Task OnInitializedAsync()
    {
        FilesInS3 = await _httpClient.GetFromJsonAsync<List<Files>>("**https://localhost:7097**/api/S3/") ?? new();
    }

    public async Task UploadFileAsync()
    {
        if (_currentFile == null)
            return;

        var content = new MultipartFormDataContent();
        var fileContent = new StreamContent(_currentFile.OpenReadStream(1024000));

        content.Add(
            content: fileContent,
            name: "\"file\"",
            fileName: _currentFile.Name);

        var response = await _httpClient.PostAsync("**https://localhost:7097**/api/S3/upload", content);
        response.EnsureSuccessStatusCode();
        FilesInS3 = await _httpClient.GetFromJsonAsync<List<Files>>("https://localhost:7097/api/S3/") ?? new();
    }
}

```
If you have a look at the `@code` part above, 

we are having a list of `Files` class instances that will be assigned at the page initialization life cycle. The value of the list of files is retrieved from the S3 via the **GET** API we created in the last chapter. If you see, I'm using the base address "**https://localhost:7097**" in the code but it will be different on your pc. You have to change it accordingly.

Next, we have the `UploadFileAsync()` button that will upload the file user has chosen using the **POST** API we created in the last chapter. We are passing the file as a Stream to that API body.

**1024000** is nothing but the maximum file size I can upload. For this demo, I'm setting up 1MB as the maximum file size I can upload. But it will vary based on the requirement.

**_currentFile** is the object that holds information about the current file chosen by the user in the UI. This is set from the HTML Code

`<InputFile **OnChange="(e) => _currentFile = e.File"** class="btn-light mb-2 w-100" />` 

That's it. To test the application, we need our API running first. 
After running both the APIs and the Blazor app, you should see the Blazor app like the one below,

![Running Blazor App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dariq9fzmhxr97jregi.png)

To upload a new file, the user will choose a file by pressing the "Choose File" button and then press the "UploadFile" button.

![Upload File](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cgghetmuom6d56x1od1c.png)

Please add your questions/suggestion in the comments.
Thanks!