Clase para representar elementos del formulario
public class CampoFormulario
{
public string NombreCampo { get; set; }
public string ValorTexto { get; set; }
public string NombreArchivo { get; set; }
public Stream DatosArchivo { get; set; }
public bool EsArchivo
{
get
{
if (DatosArchivo == null) return false;
if (string.IsNullOrWhiteSpace(NombreArchivo))
throw new InvalidOperationException("NombreArchivo es obligatorio cuando se envía un archivo");
return true;
}
}
}
Método principal para envío de formularios multipart
public static async Task<string> EnviarFormMultipartAsync(
string endpointUrl,
IEnumerable<CampoFormulario> campos,
int timeoutMs = 30000)
{
var separador = $"--boundary{DateTime.UtcNow.Ticks:X}";
var contenido = new MultipartFormDataContent(separador);
foreach (var campo in campos)
{
if (campo.EsArchivo)
{
var streamContent = new StreamContent(campo.DatosArchivo);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
contenido.Add(streamContent, campo.NombreCampo, campo.NombreArchivo);
}
else
{
contenido.Add(new StringContent(campo.ValorTexto ?? string.Empty), campo.NombreCampo);
}
}
using (var cliente = new HttpClient { Timeout = TimeSpan.FromMilliseconds(timeoutMs) })
{
var respuesta = await cliente.PostAsync(endpointUrl, contenido);
return await respuesta.Content.ReadAsStringAsync();
}
}
Ejemplo de uso con archivos locales
var rutaServidor = "https://api.ejemplo.com/upload";
var archivoA = @"C:\tmp\documento1.pdf";
var archivoB = @"C:\tmp\documento2.pdf";
var camposForm = new List<CampoFormulario>
{
new CampoFormulario
{
NombreCampo = "archivo1",
NombreArchivo = "documento1.pdf",
DatosArchivo = File.OpenRead(archivoA)
},
new CampoFormulario
{
NombreCampo = "archivo2",
NombreArchivo = "documento2.pdf",
DatosArchivo = File.OpenRead(archivoB)
},
new CampoFormulario
{
NombreCampo = "descripcion",
ValorTexto = "Archivos de prueba para integración"
},
new CampoFormulario
{
NombreCampo = "usuario",
ValorTexto = "usr_12345"
}
};
var resultado = await EnviarFormMultipartAsync(rutaServidor, camposForm);
Manejo de archivos desde URLs remotas
Cuando el origan del archivo no es una ruta local sino una URL de un servidor de archivos, se debe descargar primero a una ubicación temporal y posteriormente procesarlo como stream para su reenvío.
public static async Task<Stream> DescargarComoStreamAsync(string urlRemota)
{
var rutaTemporal = Path.Combine(Path.GetTempPath(), $"descarga_{Guid.NewGuid():N}.tmp");
try
{
using (var clienteHttp = new HttpClient())
{
var datosBinarios = await clienteHttp.GetByteArrayAsync(urlRemota);
await File.WriteAllBytesAsync(rutaTemporal, datosBinarios);
}
var flujoMemoria = new MemoryStream();
using (var flujoArchivo = File.OpenRead(rutaTemporal))
{
await flujoArchivo.CopyToAsync(flujoMemoria);
}
flujoMemoria.Position = 0;
return flujoMemoria;
}
finally
{
if (File.Exists(rutaTemporal))
File.Delete(rutaTemporal);
}
}
Uso combinado de descarga remota y subida al API destino:
var urlOrigen = "https://servidor-archivos.com/recurso/datos.csv";
var urlDestino = "https://api.tercero.com/v2/cargar";
var flujoDescargado = await DescargarComoStreamAsync(urlOrigen);
var campos = new List<CampoFormulario>
{
new CampoFormulario
{
NombreCampo = "file",
NombreArchivo = "datos.csv",
DatosArchivo = flujoDescargado
},
new CampoFormulario
{
NombreCampo = "tipo",
ValorTexto = "csv"
}
};
var respuesta = await EnviarFormMultipartAsync(urlDestino, campos);