Listing zip file content in Blazor

Pier-Luc Bonneville
Pier-Luc Bonneville
Being technical is important at the leadership level in a world where IT is eating the world.
Nov 11, 2020 3 min read
thumbnail for this post
Photo by Michael Jasmund

This is the first post in a series on working with dotnet projects directly in your browser.

Introduction

Zip files are ubiquitous these days and are one of the (if not the) de facto ways to simultaneously send a collection of files altogether.

In this post, we’ll see how to enumerate and display zip file content in a Blazor WASM application.

The solution

Here is a simple way to achieve this using a flattening the zip structure:

Index.razor

On this page, we have an input file component that accepts zip file.

@page "/"

<h1>Hello, world!</h1>

<div class="drag-drop-zone">
    <InputFile OnChange="OnInputFileChange" accept=".zip" />
    @_status
</div>

@if (_fileName != null)
{
    <h3>@_fileName</h3>
    if (_entries.Any())
    {
        <ul>
            @foreach (var item in _entries.Where(x => !x.Name.EndsWith("/")))
            {
                <li>
                    <h4>@item.Name</h4>
                    <pre>@item.Content</pre>
                </li>
            }
        </ul>
    }
}

Once the file has been “uploaded” it’s content will be displayed in a list.

Index.razor.cs

This is the backend of the Index.razor page. On file upload, pass the file stream to the ZipService.

public partial class Index
{
    private const string DefaultStatus = "Choose a zip file";

    private List<ZipEntry> _entries;
    private string _fileName;

    private string _status = DefaultStatus;

    [Inject] private ZipService ZipService { get; set; }

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        await using var stream = e.File.OpenReadStream();

        _entries = await ZipService.ExtractFiles(stream);
        _fileName = e.File.Name;

        _status = DefaultStatus;
    }
}

ZipEntry.cs

This is the model we’ll be using to represent the zip file entries.

public record ZipEntry
{
    public string Name { get; init; }
    public string Content { get; init; }
}

ZipService.cs

The ZipService class reads a zip file stream and returns a list of ZipEntry.

public class ZipService
{
    public async Task<List<ZipEntry>> ExtractFiles(Stream fileData)
    {
        await using var ms = new MemoryStream();
        await fileData.CopyToAsync(ms);

        using var archive = new ZipArchive(ms);

        var entries = new List<ZipEntry>();

        foreach (var entry in archive.Entries)
        {
            await using var fileStream = entry.Open();
            var fileBytes = await fileStream.ReadFully();
            var content = Encoding.UTF8.GetString(fileBytes);

            entries.Add(new ZipEntry { Name = entry.FullName, Content = content });
        }

        return entries;
    }
}

public static class StreamExtension
{
    public static async Task<byte[]> ReadFully(this Stream input)
    {
        await using var ms = new MemoryStream();
        await input.CopyToAsync(ms);
        return ms.ToArray();
    }
}

Remember to register your service

Let’s register our ZipService in the Program class.

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("#app");

        builder.Services.AddScoped(
            sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

        builder.Services.AddScoped<ZipService>();

        await builder.Build().RunAsync();
    }
}

Conclusion

Here is a demo of the finished result:

demo

Understandably, this won’t work with encrypted files.

The code covered in this blog post is available here:

blog-examples/tree/master/listing-zip-file-content-in-blazor

References

  1. How to: Compress and extract files
  2. ASP.NET Core Blazor file uploads