Simulação de Envio de Formulário com C#: Upload de Arquivos e Dados via POST

Para enviar formulários com upload de arquvios em C# usando HTTP, é essencial configurar a requisição com o tipo de conteúdo multipart/form-data e definir um parâmetro de delimitador (boundary) único. O servidor HTTP utiliza esse delimitador para separar as partes da mensagem, onde cada parte inclui cabeçalhos como Content-Disposition e opcionalmente Content-Type. A estrutura básica requer que cada parte comece com --[delimitador] e termine com um marcador final --[delimitador]--. Os dados textuais são representados como pares chave-valor, enquanto os arquivos são incluídos como fluxos binários, com cabeçalhos específicos para nome do arquivo e tipo MIME.

Para implementar isso em C#, define-se uma classe para modelar os itens do formulário e um método para construir e enviar a requisição POST. A geração do delimitador pode usar DateTime.Now.Ticks para garantir unicidade. O corpo da requisição é montado em um fluxo de memória, adicionando cada parte com seus respcetivos cabeçalhos e conteúdo. Para arquivos, os bytes são lidos e escritos diretamente no fluxo, enquanto para dados textuais, os valores são convertidos em bytes UTF-8.

public class ModeloItemFormulario
{
    public string Chave { get; set; }
    public string Valor { get; set; }
    public bool EhArquivo
    {
        get
        {
            if (ConteudoArquivo == null || ConteudoArquivo.Length == 0)
                return false;
            if (string.IsNullOrWhiteSpace(NomeArquivo))
                throw new InvalidOperationException("O nome do arquivo é obrigatório ao enviar um arquivo.");
            return true;
        }
    }
    public string NomeArquivo { get; set; }
    public Stream ConteudoArquivo { get; set; }
}

public static string EnviarFormulario(string url, List<ModeloItemFormulario> itensFormulario, CookieContainer containerCookies = null, string urlReferencia = null, Encoding codificacao = null, int tempoEspera = 20000)
{
    var requisicao = (HttpWebRequest)WebRequest.Create(url);
    requisicao.Method = "POST";
    requisicao.Timeout = tempoEspera;
    requisicao.KeepAlive = true;
    requisicao.UserAgent = "Mozilla/5.0 (compatible; Exemplo/1.0)";
    if (!string.IsNullOrEmpty(urlReferencia))
        requisicao.Referer = urlReferencia;
    if (containerCookies != null)
        requisicao.CookieContainer = containerCookies;

    string delimitador = "----" + DateTime.Now.Ticks.ToString("x");
    requisicao.ContentType = $"multipart/form-data; boundary={delimitador}";

    using (var fluxoPost = new MemoryStream())
    {
        if (itensFormulario != null && itensFormulario.Count > 0)
        {
            string templateArquivo = $"\r\n--{delimitador}\r\nContent-Disposition: form-data; name=\"{{0}}\"; filename=\"{{1}}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
            string templateDados = $"\r\n--{delimitador}\r\nContent-Disposition: form-data; name=\"{{0}}\"\r\n\r\n{{1}}";

            // Escrever o primeiro marcador de delimitador
            byte[] cabecalhoDelimitador = Encoding.UTF8.GetBytes("--" + delimitador);
            fluxoPost.Write(cabecalhoDelimitador, 0, cabecalhoDelimitador.Length);

            foreach (var item in itensFormulario)
            {
                string parteFormulario = item.EhArquivo
                    ? string.Format(templateArquivo, item.Chave, item.NomeArquivo)
                    : string.Format(templateDados, item.Chave, item.Valor);

                byte[] bytesParte = Encoding.UTF8.GetBytes(parteFormulario);
                fluxoPost.Write(bytesParte, 0, bytesParte.Length);

                if (item.EhArquivo && item.ConteudoArquivo != null)
                {
                    using (var fluxoArquivo = item.ConteudoArquivo)
                    {
                        byte[] bufferLeitura = new byte[4096];
                        int bytesLidos;
                        while ((bytesLidos = fluxoArquivo.Read(bufferLeitura, 0, bufferLeitura.Length)) > 0)
                        {
                            fluxoPost.Write(bufferLeitura, 0, bytesLidos);
                        }
                    }
                }
            }

            byte[] rodapeDelimitador = Encoding.UTF8.GetBytes($"\r\n--{delimitador}--\r\n");
            fluxoPost.Write(rodapeDelimitador, 0, rodapeDelimitador.Length);
        }
        else
        {
            requisicao.ContentType = "application/x-www-form-urlencoded";
        }

        requisicao.ContentLength = fluxoPost.Length;
        fluxoPost.Position = 0;

        using (var fluxoSolicitacao = requisicao.GetRequestStream())
        {
            byte[] bufferEnvio = new byte[4096];
            int bytesEnviados;
            while ((bytesEnviados = fluxoPost.Read(bufferEnvio, 0, bufferEnvio.Length)) > 0)
            {
                fluxoSolicitacao.Write(bufferEnvio, 0, bytesEnviados);
            }
        }
    }

    using (var resposta = (HttpWebResponse)requisicao.GetResponse())
    {
        if (containerCookies != null)
        {
            containerCookies.GetCookies(resposta.ResponseUri);
        }

        using (var fluxoResposta = resposta.GetResponseStream())
        using (var leitorResposta = new StreamReader(fluxoResposta, codificacao ?? Encoding.UTF8))
        {
            return leitorResposta.ReadToEnd();
        }
    }
}

Para utilizar o método, crie instâncias de ModeloItemFormulario para cada dado ou arquivo a ser anviado, incluindo a chave do formulário, o valor ou o conteúdo do arquivo via fluxo. Em seguida, passe a lista para o método EnviarFormulario com a URL alvo e parâmetros opcionais como cookies ou tempo de espera.

var urlDestino = "http://exemplo.com/envio";
var caminhoArquivo1 = @"C:\dados\documento1.pdf";
var caminhoArquivo2 = @"C:\dados\foto.jpg";
var listaItens = new List<ModeloItemFormulario>
{
    new ModeloItemFormulario { Chave = "anexo1", NomeArquivo = "documento1.pdf", ConteudoArquivo = File.OpenRead(caminhoArquivo1) },
    new ModeloItemFormulario { Chave = "anexo2", NomeArquivo = "foto.jpg", ConteudoArquivo = File.OpenRead(caminhoArquivo2) },
    new ModeloItemFormulario { Chave = "titulo", Valor = "Exemplo de envio" },
    new ModeloItemFormulario { Chave = "descricao", Valor = "Dados textuais do formulário" }
};

var respostaServidor = EnviarFormulario(urlDestino, listaItens);

Tags: C# HTTP formulário multipart upload de arquivos POST

Publicado em 6-1 15:48 por Thomas