Multi-Upload com ASP.NET usando JQuery e escrita na sessão do Handler

Apesar do título grande e, para uns, confuso, nesse tópico irei ajudar muitos a resolver um problema: múltiplos uploads de vez sem precisar selecionar cada arquivo.



A idéia primordial é fazer com que o usuário possa clicar em um botão, selecionar vários arquivos de vez (que nem o GMail) e fazer o envio para uma pasta desejada. Para isso iremos utilizar um Handler em C# para fazer o upload. Mas nesse mesmo Handler irei escrever na sessão para que possa utilizar em outra página. Lembrando que as variáveis de sessão, para usar dentro de um Handler precisa-se de um artifício. Sem mais delongas, vamos à solução.

Crie ou abra um Web Site Project e nele adicione um Generic Handler. Coloque com o nome Upload.ashx para ficar mais fácil de encontrá-lo. Insira o seguinte cógido:

using System;
using System.Data;
using System.IO;
using System.Web;
using System.Web.SessionState;

public class Upload : IHttpHandler, IRequiresSessionState{

        public void ProcessRequest (HttpContext context) {
                context.Response.ContentType = "text/plain";
                context.Response.Expires = -1;
                try
                {
                        // Obtêm o arquivo a ser postado
                        HttpPostedFile postedFile = context.Request.Files["filedata"];
                        // Caminho
                        string path = context.Server.MapPath("~/Upload/");
                        string filename = postedFile.FileName;
                        // Cria diretório de não existir
                        if (!Directory.Exists(path))
                                Directory.CreateDirectory(path);
                        // Verifica se o arquivo já existe com mesmo nome
                        string nome = filename.Substring(0, filename.LastIndexOf("."));
                        string extensao = filename.Substring(filename.LastIndexOf(".") + 1);
                        int i = 1;
                        // Verifica se existe, senão renomeia
                        while (File.Exists(path + @"\" + filename))
                        {
                                filename = nome + "(" + i + ")." + extensao;
                                i++;
                        }
                        // Adiciona na sessão
                        DataTable galeria = (DataTable)context.Session["galeria"];
                        DataRow item = galeria.NewRow();
                        item["imagem"] = filename;
                        galeria.Rows.Add(item);
                        context.Session["galeria"] = galeria;
                        // Salva arquivo
                        postedFile.SaveAs(path + @"\" + filename);
                        // Retorna o arquivo salvo
                        context.Response.Write(path + "/" + filename);
                        context.Response.StatusCode = 200;
                }
                catch (Exception ex)
                {
                        context.Response.Write("Erro: " + ex.Message);
                }
        }


        public bool IsReusable {
                get {
                       return true;
                }
        }

}
 
Pronto! Já temos nosso Handler que faz o upload de um arquivo e o adiciona em um DataTable. Esse DataTable já estava previamente adicionado na sessão. Você pode utilizar quaisquer outra estrutura para armazenar. A dica da escrita na sessão no Handler está na herança da interface IRequiresSessionState onde permitirá fazer isso.
 
Crie uma página ASPX e referencie os JavaScripts do JQuery abaixo (os arquivos, caso não possuam, irei disponibilizar em um link abaixo) e um CSS pra dar um estilo legal:

<link rel="stylesheet" type="text/css" href="css/stilo.css" />
<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="js/jquery.uploadify.js"></script>

Agora adicione um controle FileUpload e um link (ou um controle HyperLink). Iremos também fazer a chamada do JQuery para ser aplicado sobre o FileUpload ficando dessa forma:

<asp:FileUpload ID="Fotos" runat="server" />
<input type="button" onclick="javascript:$('#<%=Fotos.ClientID%>').fileUploadStart();" value="Enviar" />
<script type="text/javascript">
$(window).load(
       function() {
       $("#<%=Fotos.ClientID %>").fileUpload({
           'uploader': 'js/uploader.swf',
           'cancelImg': 'imagens/cancelar.png',
           'buttonText': 'Selecionar',
           'script': 'Upload.ashx',
           'folder': 'Upload',
           'fileDesc': 'Selecione arquivos do tipo imagem',
           'fileExt': '*.jpg;*.jpeg;*.gif;*.png',
           'multi': true,
           'auto': false
       });
      }
);
</script>

Agora rode a aplicação. Verá apenas um botão com o nome Selecionar. Ao clicar irá abrir um pop-up pedindo para selecionar os arquivos. Selecione um ou mais e clique em OK. Verá que será montado uma lista com os arquivos que irão ser enviados. Você poderá remover itens clicando no X sobre cada um deles. Ao clicar no link Enviar, será feito uma chamada para cada item sobre o Handler fazendo o envio para a pasta Upload e adicionando no DataTable armazenado na sessão.

Não vou entrar muito em datelhes, porque está bem fácil e compreensível o código. Se desejar, ao invés de clicar em Enviar para fazer o envio, você pode deixar automático (como se fosse uma trigger) mudando o parâmetro 'auto' para true da chamada JQuery. Então, ao selecionar e clicar em OK automaticamente será feito o upload. Se também quiser que o link faça um PostBack na página, basta colocar depois da chamada de início do upload, insira o __doPostBack('',''); colocando parâmetro ou não dependendo do que for fazer com ele após.

Nesse link aqui encontram-se alguns arquivos necessários para que possam utilizar. Não vou colocar os fontes ASP.NET porque senão já seria demais, né? Mãos à obra...

Obs: a fonte e outras matérias sobre o mesmo tópico você acha aí pela net. Não me lembro qual site eu havia visto algo do tipo e fiz essa. Mas fica a dica aí!

14 comentários:

Anônimo disse...

muito bom este artigo.
parabéns

Unknown disse...

Muito bom o Artigo mas tenho uma dúvida o fato de vc herdar a IRequiresSessionState não faz com o handler perca a sua leveza ou isto fica pouco percepetível ?

Thiago Marçal disse...

Raul, não notei a diferença no desempenho pois não fiz um teste de carga. Posso fazer depois e lhe dar um retorno, mas creio que, para aplicações pequenas que demandam uma vez ou outra esse tipo de chamada, não deve ter um impacto muito grande na aplicação. Obrigado pela dúvida...

Zanella disse...

Olá Thiago...
Excelentes suas dicas...
Usei seu exemplo, com algumas alterações, mas estou tendo um probleminha...
Alterei para fazer upload de arquivos .txt e .mdb...
Os txt ele upa de boa, mas os mdb dá mensagem de IO error...
pode me dar uma luz?

Thiago Marçal disse...

Veja qual o tamanho da base MDB que está enviando e as permissões na pasta de destino. No web.config aplique:



Para aumentar o tempo e o tamanho dos arquivos enviados. Teste novamente e se não conseguir me envie mais detalhes do erro e/ou rastreamento da pilha...

Thiago Marçal disse...

Não saiu a tag... Segue abaixo:

httpRuntime maxRequestLength="400000" executionTimeout="100"

Zanella disse...

somente o maxRequestLength resolveu!
era o tamanho do arquivo mesmo!!!
muito obrigado!

Anônimo disse...

Thiago, parabéns pelo excelente código.
Estou implementando em meu site, mas esbarrei em um problema: ao limitar o tamanho do arquivo utilizando a propriedade "sizeLimit" recebo o aviso "File size error" corretamente, mas aí não consigo mais excluir utilizando o ícone correspondente. Não dá erro, apenas não exclui.
Vc pode me ajudar? Aproveitando essa mensagem é do meu sistema operacional? Posso configura-la para português?

Anônimo disse...

Obrigado pela informação. Isso mais o codigo que tenho pra redimensionar a imagem ANTES do upload, no lado do cliente, ficou perfeito!

Valeu

Claudinei Santos disse...

Thiago bom dia,

muito bom o artigo. Implementei do jeito como mandou o figurino. Sendo que ele, por algum motivo nao faz o upload do(s) arquivo(s). O mais estranho é que ele no debug dá tudo certo, tanto que indica o caminho, o streaming, tamanho do arquivo. O que poderia ser?

Thiago Marçal disse...

Claudinei,
Dê permissão de escrita para o usuário Todos (Everyone) na pasta onde irá armazenar os uploads e tente novamente. Veja nos comentários acima algumas das soluções para esse caso. Se não conseguir, me envie seu código para verificação e teste.

Carolina Cj Rio Verde disse...

Ótimo artigo! Me ajudou muito, porém, só consigo fazer o upload com a aplicação rodando local. Quando jogo em um servidor ftp, sempre dá erro. Tento mandar usando stream, mas não localiza o arquivo na máquina. Tento usar o SaveAs(), mas não aceita salvar no servidor.
Você sabe o que pode ser?

Thiago Marçal disse...

Carolina, já tentou dar permissão de escrita na pasta do servidor onde irá salvar as imagens? Aplique as permissões e tente no novamente. De qualquer forma, qual o erro que está dando?

Unknown disse...

Como posso renomear o arquivo na hora do envio?? tipo 21_1, 21_2?

Postar um comentário