5 de dez. de 2008

Download de arquivos dinâmicos

Download de arquivos gerados dinamicamente é uma rotina comum em muitos sites.

Já precisei fazer isso diversas vezes e o código normalmente se parece com isso:

Response.AddHeader("Content-Size", bytArq.Length.ToString());
Response.AddHeader("Content-Disposition", "attachment; filename=" +
NomeArquivo);
Response.OutputStream.Write(bytArq, 0, bytArq.Length);Response.End();
onde:
byteArq é um byte [] do conteudo do arquivo, sendo ele texto ou binário 
NomeArquivo é a o nome padrão sugerido pelo browser ao gravar o arquivo.
Até aí nenhuma novidade, mas vamos aos desafios.
Qual o problema do código acima ?
Se eu quisesse fazer o download do arquivo e logo em seguida atualizar algum campo na página, como por exemplo um label "Arquivo enviado com sucesso" ?

Com o código acima, não seria possível.


A linha Response.End(), interrompe a execução da página, e faz com que qualquer código logo após seja ignorado (pelo menos código que gere html de retorno).


Por quê essa linha é necessária ?


Porque sem ela o código no .cs seria executado e logo após o browser iria redenderizar a execução do aspx, o que faria com que o resultado gerado seria a combinação do arquivo mais o html, corrompendo o arquivo.


Na prática não é possível enviar 2 conteúdos ao browser simultaneamente, ou seja, ou eu envio o html, ou envio o arquivo.

Vamos aos workarounds:

E se ao invés de escrever os bytes do arquivo, eu abrisse uma janela, onde lá sim, escreveria o byte array, e na minha página principal continuaria meu código?

A solução seria:

pagina.aspx, onde faria a chamada do download do arquivo
download.aspx, um arquivo sem html no aspx


No código na pagina.aspx:
Um método para escrever abrir a página de download

void DownloadArquivo(Page p, byte[] bytArq, string NomeArquivo)
{
Session["downloadBytes"] = bytArq;
Session["downloadName"] = NomeArquivo;
System.Text.StringBuilder s = new System.Text.StringBuilder();
s.Append("\n<SCRIPT LANGUAGE='JavaScript'>\n");
s.Append("function download()");
s.Append("{\n");
s.Append(" window.open('download.aspx', 'Download', 'toolbar=no,
location=no, directories=no, status=yes, menubar=no, scrollbars=no,
resizable=no, titlebar=no, copyhistory=no, width=100, height=100, left=0,
top=0');\n"
);
s.Append("}\n");
s.Append("window.onload = download;\n");
s.Append("</SCRIPT>");
p.RegisterClientScriptBlock("download", s.ToString());
}


A chamada desse método é bem simples, em qualquer botão de processamento e download seria:

// chame seu metodo que gere o arquivo ou pegue-o do banco de dados 
byte
[] arq = GeraSeuArquivo();
DownloadArquivo(this, arq, "meuarquivo.ext");
lblMeuLabel.Text = "Pronto o arquivo foi enviado para o browser";

No código no download.aspx, apenas o minimo necessário

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="download.aspx.cs" Inherits="download" Title="Download..." %>

O código no codebehind dessa página teria apenas o método Page_Load()
protected void Page_Load(object sender, EventArgs e)
{
byte[] bytArq;
string NomeArquivo;
if (Session["downloadBytes"] != null)
{
bytArq = (byte[])Session["downloadBytes"];
if (bytArq.Length > 0)
{
NomeArquivo = (string)Session["downloadName"];
Session["downloadBytes"] = null;
Session["downloadName"] = null;
Response.AddHeader("Content-Disposition", "attachment; filename=" +
NomeArquivo);
Response.AddHeader("Content-Type", "application/force-download");
Response.AddHeader("Content-Type", "application/octet-stream");
Response.AddHeader("Content-Type", "application/download");
Response.AddHeader("Content-Description", "File Transfer");
Response.OutputStream.Write(bytArq, 0, bytArq.Length);
}
Response.End();
}

}

Como funciona a mágica:


A página irá gerar o arquivo (byte array) e renderizará um código javascript que abrirá um popup.


Antes de terminar a execução da pagina.aspx ela irá armazenar o conteudo desse arquivo na Session.


Ao termino da execução, o popup irá começar e executará o Page_Load


Nesse método, o download.aspx irá recuperar esse arquivo, removê-lo da Session e renderizá-lo no browser.

Simples e direto.

Limitações:


A solução funciona, mas tem suas limitações: como a transferência de conteúdo é feita através da Session, durante essa transferência, o servidor irá ocupar esse espaço de memória (mesmo que removendo logo em seguida), o que na prática quer dizer que se for transferido um arquivo muito grande, poderá ocorrer um OutOfMemory Exception.


Taí, duvido vocês testarem.

Postar um comentário