dan-kli
dan-kli

Reputation: 893

What is the best way to offer parallel file download streams from Blazor Server?

When it comes to downloading files from server to client in blazor, there does not seem to be a best practice yet (please correct me if I am wrong). One good solution seems to be to implement a controller that returns a filestream (like it is done here: How to download in-memory file from Blazor server-side), there are also more client-side oriented solutions that involve javascript (like these: How can one generate and save a file client side using Blazor?). None of these however seem to exactly fit my problem.

What I want is a solution that lets me start parallel download streams of big files from the server to the client. Currently I am using a controller that fetches and zips files from a given directory in memory. I want the user to be able to start multiple download streams from the same page client-side. This does not work with my controller currently, since it redirects the user and the user has to wait until the download is finished to start the next. What is a good way to offer parallel downloads in blazor?

Here is what I currently have (simplified):

Controller:

[ApiController]
[Route("[controller]")]
public class DownloadController : Controller
{
    
    [HttpGet("{directory}/{zipFileName}")]
    [DisableRequestSizeLimit]
    public IActionResult DownloadZipFile(string directory, string zipFileName)
    {
        directory = System.Net.WebUtility.UrlDecode(directory);
        if (Directory.Exists(directory))
        {
            using (ZipFile zip = new ZipFile())
            {
                var files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
                foreach (var f in files)
                {
                    zip.AddFile(f,Path.GetDirectoryName(f).Replace(directory, string.Empty));
                }
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    zip.Save(memoryStream);
                    return File(memoryStream.ToArray(), "application/zip", String.Format(zipFileName));
                }
            }
        }
        else
        {
            // error handling
        }
    }
}

Razor Page:

<button @onclick="()=>DownloadZip(zipFilePath)">download file</button>

@code {
    protected string zipFilePath= @"C:\path\to\files";

    protected void DownloadZip(string zipFilePath)
    {
        NavigationManager.NavigateTo("api/download/" + System.Net.WebUtility.UrlEncode(zipFilePath) + "/ZipFileName.zip", true);
    }

}

Upvotes: 0

Views: 604

Answers (1)

Mister Magoo
Mister Magoo

Reputation: 8994

Don't use a button and NavigationManager - instead use an anchor tag with the download attribute:

<a href=@GetZipURL(zipFilePath) target="_new" download>download file</a>
@code {
    protected string zipFilePath= @"C:\path\to\files";

    protected string GetZipURL(string zipFilePath)
    {
        return $"api/download/{System.Net.WebUtility.UrlEncode(zipFilePath)}/ZipFileName.zip";
    }
}

Your users can spam that download "button" as much as they like - the browser will handle parallel downloads.

If you want it to look like a button, that's just a bit of CSS styling.

Notice: The method GetZipURL is just returning a string - it is not redirecting or navigating.

I used target="_new" to prevent Blazor intercepting the event - but this is not required from .NET5 onwards.

Upvotes: 1

Related Questions