[C# .Net] Como compactar vários arquivos em um .zip e forçar o download
Para o dia a dia da empresa que trabalho (na qual também sou fundador), usamos um sistema simples de ticket system, feito em asp.net webforms, com layout baseado em bootstrap.
Esse sistema foi desenvolvido por nós mesmos da empresa, simplesmente pra ser o mais simples possível, o mais adaptado possível para as nossas realidades, para nossos sistemas.
Também serviu durante muito tempo pra introduzir pessoas da nossa empresa, no básico de desenvolvimento que usamos, algo como um treinamento básico de asp.net webforms com C# e banco de dados em SQL Server.
Contudo, esses dias eu tinha que atender um ticket que tinha 10 arquivos anexos na mensagem de abertura e eu percebi que não existia um botão "baixar todos os anexos".
Resolvi implementar.
O código inicial, que existe até hoje para baixar apenas um anexo por vez é o seguinte:
// recupera o nome do arquivo que é um 'argument' de um repeater
string nomeArquivo = e.CommandArgument.ToString();
// monta o caminho completo do arquivo no servidor, concatenando a pasta com o nome do arquivo
string caminhoCompletoArquivo = Server.MapPath("~/TicketAnexo/") + nomeArquivo;
string pattern = string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
// remove caracteres invalidos para nome de arquivos
string novoNomeArquivo = Regex.Replace(nomeArquivo, pattern, "");
string novoCaminhoCompletoArquivo = Path.Combine(Path.GetDirectoryName(caminhoCompletoArquivo), novoNomeArquivo);
FileInfo arquivo = new FileInfo(novoCaminhoCompletoArquivo);
// força o download
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + arquivo.Name);
Response.AddHeader("Content-Length", arquivo.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(arquivo.FullName);
Response.End();
Coloquei alguns comentários dentro do código, mas a ideia é basicamente encontrar o caminho físico do arquivo no servidor, iniciar uma classe 'FileInfo' e "Escrever" o arquivo no response utilizando 'Response.WriteFile(arquivo.FullName);'
Para a minha ideia de "baixar todos os anexos", eu não queria salvar os arquivos .zip depois de cria-lo. Por esse motivo o final, a parte de "escrever" o arquivo no response, ficou um pouco diferente.
O trecho de código que cria o arquivo .zip e faz um loop incluindo cada um dos arquivos que estão no ticket é o seguinte
byte[] compressedBytes;
string numeroTicket = string.Empty;
using (var outStream = new MemoryStream())
{
//cria um arquivo .zip em memória
using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
{
//loop na lista de caminhos dos arquivos anexos.
foreach (var a in anexos)
{
numeroTicket = a.TicketComentario.Ticket.NumeroTicket;
string nomeArquivo = a.Arquivo;
string caminhoCompletoArquivo = Server.MapPath("~/TicketAnexo/") + nomeArquivo;
//lê o arquivo como um array de bytes
byte[] fileBytes = File.ReadAllBytes(caminhoCompletoArquivo);
//inclui o arquivo no .zip
var fileInArchive = archive.CreateEntry(nomeArquivo, CompressionLevel.Optimal);
using (var entryStream = fileInArchive.Open())
using (var fileToCompressStream = new MemoryStream(fileBytes))
{
fileToCompressStream.CopyTo(entryStream);
}
}
}
// cria um array de bytes para o arquivo .zip
compressedBytes = outStream.ToArray();
}
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=ticket_" + numeroTicket + "_anexos.zip");
Response.AddHeader("Content-Length", compressedBytes.Length.ToString());
Response.ContentType = "application/octet-stream";
// escreve um array de bytes no response
Response.BinaryWrite(compressedBytes);
Response.End();
Espero que esse código possa ajudar alguém.
Desejo que esse relato inspire pessoas também.
Nunca é tarde para melhorar o seu trabalho, suas ferramentas, o seu dia a dia.