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);