Implement the "download file" feature on a Blazor WebAssembly app
Tags: blazor
Published: 2020-08-02 07:13:55

---
title: Implement the "download file" feature on a Blazor WebAssembly app
published: true
description: I'll show you how to implement the "download file" feature in a Blazor app. 
tags: blazor
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/m6lq31gvvdlgb51pspe6.png
---

If we want to implement the "download file" feature (please imagine a picture that was posted on Facebook can be downloadable) on your Blazor WebAssembly app project, how can we implement that feature?

# Method 1 - make `<a>` tag with `download` attribute, simply - but it doesn't work as expected.

At first, I came up with a simple solution that is making `<a>` tag linked with the URL of the file, and add `download` attribute to that tag.

```html
<a href="api/pictures/1" download="foo.jpeg">
  download
</a>
```

Unfortunately, this solution doesn't work as expected.

Please remind that the runtime of Blazor intercepts all of URL navigations, and transfer it to rendering a component which has a matching route URL.

If there is no matching route URL, the URL navigation is passed through to the web browser.

However, in this scenario, **Blazor doesn't consider the `download` attribute!**

Therefore, if the user clicks this `<a>` tag, the user will see the picture file inside the browser window, **downloading the file is not started.**

![fig.1](https://dev-to-uploads.s3.amazonaws.com/i/2sv95jdxlm1uuu0n5yo4.png)

To be exact, if the content type of the linked file is not well known for the web browser, it will be started the download.

But even if this scenario, this behavior will also give us a bad experience, because the URL of the address bar will be changed from the URL of the current page to the URL of the file.

In addtion, **the download file name that is specified in `download` attribute is ignored.**

# Method 2 - make `<a>` tag with `download` attribute, and ... `target="_top"` attribute!

To avoid the interception of URL navigation by Blazor, we can use a hack.

The hack is, adding `target="_top"` attribute to the `<a>` tag.

```html
<a href="api/pictures/1" download="foo.jpeg" target="_top">
  download
</a>
```

Blazor doesn't intercept URL navigation in some cases, and adding `target="_top"` attribute is one of those cases.

This solution is very simple, and works fine as expected, as perfect.

# Method 3 - use JavaScript helper function

By the way, in some cases, you may want to confirm to the user before downloading started.

To implement this, we have to use the JavaScript helper function.

At first, implement the JavaScript function that receives the download URL and file name, and that downloads form the URL to a file with a specified file name, like this.

_(Aside: I always use TypeScript to implement JavaScript code.)_

```ts
// helper.ts 
// (this TypeScript source file will be trancepiled to "helper.js")

function downloadFromUrl(options: { url: string, fileName?: string }): void {
  const anchorElement = document.createElement('a');
  anchorElement.href = options.url;
  anchorElement.download = options.fileName ?? '';
  anchorElement.click();
  anchorElement.remove();
}
```

This JavaScript code is a well known and classical technic.

After implementing this JavaScript code, don't forget to include it from the HTML document.

```html
  ...
  <script src="_framework/blazor.webassembly.js"></script>
  <script src="script/helper.js"></script>
</body>
</html>
```

At last, just invoke that JavaScript helper function from C# code in Blazor app.

```csharp
@inject IJSRuntime JSRuntime
...
<button @onclick="OnClickDownloadButton">
  download
</button>
...
@code {
  private async Task OnClickDownloadButton()
  {
    var res = await JSRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
    if (res == false) return;

    await JSRuntime.InvokeVoidAsync(
      "downloadFromUrl",
      new {Url = "api/pictures/1", FileName = "foo.jpeg"});
  }
}
```

# Method 4 - download from in-memory byte array inside of Blazor app

In some cases, the web browser can not download from the resource URL directly.

For example:

- The resource URL is protected by token-based (non cookie-based) authorization, therefore it can be retrieved only from HttpClient inside the Blazor app.
- The contents to download is generated by computing in C# code in Blazor app.

In these cases, we have to make in-memory byte array contents to be downloadable.

To do this, we can use **"object URL"** feature of Web browser and JavaScript engine on it.

`URL.createObjectURL()` API allows us to make a valid URL (it's called "object URL") that is linked to the "Blob" object.

- ["URL.createObjectURL() - Web APIs | MDN"](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)

By the way, there is a big problem.

No way transfers the byte array from inside of the Blazor app to JavaScript natively. Instead, the byte array inside of the Blazor app is converted base64 encoded string by JavaScript interop feature of the Blazor runtime, before pass it to a JavaScript function.

After thinking these considerations points, I implemented the JavaScript helper function, like this:

```ts
// helper.ts
...

function downloadFromByteArray(options: { 
  byteArray: string, 
  fileName: string, 
  contentType: string
}): void {

  // Convert base64 string to numbers array.
  const numArray = atob(options.byteArray).split('').map(c => c.charCodeAt(0));

  // Convert numbers array to Uint8Array object.
  const uint8Array = new Uint8Array(numArray);

  // Wrap it by Blob object.
  const blob = new Blob([uint8Array], { type: options.contentType });

  // Create "object URL" that is linked to the Blob object.
  const url = URL.createObjectURL(blob);

  // Invoke download helper function that implemented in 
  // the earlier section of this article.
  downloadFromUrl({ url: url, fileName: options.fileName });

  // At last, release unused resources.
  URL.revokeObjectURL(url);
}
```

After implemented this JavaScript helper function, we can invoke it from C# code in the Blazor app, like this:

```csharp
@inject HttpClient HttpClient
@inject IJSRuntime JSRuntime
...
<button @onclick="OnClickDownloadButton">
  download
</button>
...
@code {
  private async Task OnClickDownloadButton()
  {
    // Please imagine the situation that the API is protected by
    // token-based authorization (non cookie-based authorization).
    var bytes = await HttpClient.GetByteArrayAsync("api/pictures/1");

    await JSRuntime.InvokeVoidAsync(
      "downloadFromByteArray",
      new
      {
          ByteArray = bytes,
          FileName = "foo.jpeg",
          ContentType = "image/jpeg"
      });
  }
}
```

By using these implementations, we can make a byte array to downloadable.

However, please remember **these implementations are very inefficient**.  
Because these implementations cause **duplicating source byte array many times**.

Please remind that:

1. source byte array is encoded to base64 string. (source byte array is duplicated as another representation.)
2. The base64 string is copied from Blazor runtime to JavaScript runtime. (source byte array is duplicated, again.)
3. The base64 string is decoded to number array. (source byte array is duplicated as another representation, again.)
4. (etc...)


# Conclusion

I showed you how to implement the "download file" feature in a Blazor app in this article.

All of the sample code is published at the GitHub repository below.

- https://github.com/sample-by-jsakamoto/Blazor-DownloadFIles

If you know more efficiently implementation, please feel free to get in touch with me, and let's share the technic.

Happy coding! :)