public async Task OnPostDownloadZipAsync(CancellationToken cancellationToken) { if (!OrgName.TryCreate(User.GetCompanyName(), out var orgName)) return NotFound(); var fallback = RedirectToPage(new { q = Search, types = TypeFilterSelection, pageSize = PageSize, pageToken = PageToken, }); if (SelectedDocuments is null || SelectedDocuments.Count == 0) return fallback; var relativePaths = SelectedDocuments .SelectWhere(x => new DocumentId(x).TryDecode(out var y) ? y.Value : null) .Where(x => IsDownloadable(x.Name)) .ToList(); if (relativePaths.Count == 0) return fallback; var orgSlug = await documents.ResolveCustomerPrefixAsync(OrgId, orgName.Value, cancellationToken); if (orgSlug is null) return fallback; // Zipping compresses each entry with a DeflateStream, which flushes its buffers synchronously // when the entry is closed. Kestrel forbids synchronous response writes by default, so we opt // in for this streamed response only. Nothing large is written synchronously - the document // payloads are still copied with async IO. var bodyControl = HttpContext.Features.Get(); bodyControl?.AllowSynchronousIO = true; Response.ContentType = "application/zip"; Response.Headers.ContentDisposition = $"attachment; filename=\"{DocumentsService.ZipDownloadFileName}\""; await documents.WriteZipArchiveAsync(orgSlug.Value, relativePaths, Response.Body, cancellationToken); return new EmptyResult(); }