Zip the whole ASP.NET Core application

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

I realized that I should have somehow let the user play with the application from his PC. How can I do that , if the application is on some site  ? Answer: download the whole site as a zip file.

It should be relatively easy  for the user– so an endpoint routing should be used – something like “/zip”

It should be relatively easy  for the programmer – so I figured

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();

endpoints.MapZip(app); // IApplicationBuilder app
});

The code is also relativeley simple: enumerating recursive file and folders , creating the zip archive in memory, sending to the user

public static void MapZip(this IEndpointRouteBuilder endpoints, IApplicationBuilder app)
{
//see more at
//https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/
endpoints.Map(“/zip”, async context =>
{
var response = context.Response;
var name = Assembly.GetEntryAssembly().GetName().Name + “.zip”;
response.ContentType = “application/octet-stream”;
var b = GetZip(app.ApplicationServices.GetService<IWebHostEnvironment>());
//https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs
var contentDisposition = new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue(“attachment”);
contentDisposition.SetHttpFileName(name);
response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
await response.Body.WriteAsync(b);

});

}

private static Memory<byte> GetZip(IWebHostEnvironment env)
{
//var b = new Memory<byte>(Encoding.ASCII.GetBytes($”{env.ContentRootPath}”));
var firstDir = new DirectoryInfo(env.ContentRootPath);
var nameLength = firstDir.FullName.Length + 1;
using var memoryStream = new MemoryStream();
using var zipToOpen = new ZipArchive(memoryStream, ZipArchiveMode.Create, true);

 

foreach (FileInfo file in firstDir.RecursiveFilesAndFolders().Where(o => o is FileInfo).Cast<FileInfo>())
{
var relPath = file.FullName.Substring(nameLength);
var readmeEntry = zipToOpen.CreateEntryFromFile(file.FullName, relPath);
}
zipToOpen.Dispose();
var b = new Memory<byte>(memoryStream.ToArray());
return b;

}

private static IEnumerable<FileSystemInfo> RecursiveFilesAndFolders(this DirectoryInfo dir)
{
foreach (var f in dir.GetFiles())
yield return f;
foreach (var d in dir.GetDirectories())
{
yield return d;
foreach (var o in RecursiveFilesAndFolders(d))
yield return o;
}
}

 

Some points to be noted:

1. It will work only if the site and user have the same operating system( assuming you have did self contained the .NET Core application)

2. ContentDispositionHeaderValue – exists in 2 libraries

3. I have shameless copy code from StackOverflow and github aspnet

4. I did not put in a separate library, nor I do automatic tests. Sorry, too tired

5. It works only if you put

endpoints.MapZip(app); // IApplicationBuilder app

If not, it does not zip!

6. IEndpointRouteBuilder – it is defined only you have the library .net core  3, not .net standard 2

<TargetFramework>netcoreapp3.1</TargetFramework>

<ItemGroup>
<FrameworkReference Include=”Microsoft.AspNetCore.App” />
</ItemGroup>