Subida de archivos mediante multipart/form-data en C# hacia APIs externas

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

Etiquetas: C# HttpClient multipart-form-data REST API file-upload

Publicado el 6-30 16:11