C # HttpClient 4.5 multiparte / carga de datos de formulario

145

¿Alguien sabe cómo usar HttpClientin .Net 4.5 con multipart/form-dataupload?

No pude encontrar ningún ejemplo en Internet.

ident
fuente
1
Lo intenté pero no tengo idea de cómo iniciarlo ... dónde agrego el byteArray al contenido, etc. Necesito una especie de ayuda de inicio.
ident
Puedes mirar la respuesta de este post. (Con configuración de Proxy) stackoverflow.com/a/50462636/2123797
Ergin Çelik

Respuestas:

156

mi resultado se ve así:

public static async Task<string> Upload(byte[] image)
{
     using (var client = new HttpClient())
     {
         using (var content =
             new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
         {
             content.Add(new StreamContent(new MemoryStream(image)), "bilddatei", "upload.jpg");

              using (
                 var message =
                     await client.PostAsync("http://www.directupload.net/index.php?mode=upload", content))
              {
                  var input = await message.Content.ReadAsStringAsync();

                  return !string.IsNullOrWhiteSpace(input) ? Regex.Match(input, @"http://\w*\.directupload\.net/images/\d*/\w*\.[a-z]{3}").Value : null;
              }
          }
     }
}
ident
fuente
66
Wow, es mucho más simple hacer esto al cargar archivos grandes en la API REST. No me gusta comentar gracias, pero gracias. Es portátil para Windows Phone 8.
Léon Pelletier
1
Este código falló para mí ya que la cadena de límites pasada new MultipartFormDataContent(...)contenía un carácter de límite no válido (tal vez el separador "/"). Sin errores, simplemente no hay archivos publicados en el servidor; en mi caso, Context.Request.Files.Count = 0 en el controlador API. Posiblemente solo sea un Nancyproblema, pero sugiero usar algo como en su DateTime.Now.Ticks.ToString("x")lugar.
Dunc
77
@MauricioAviles, tu enlace está roto. Encontré este que lo explicaba muy bien: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
Kevin Harker
1
Si recibe un error: " No se encontraron archivos cargados ", intente agregar los parámetros keyy fileNamea content( bilddatei y upload.jpg en este ejemplo).
jhhwilliams
1
@KevinHarker, relee el segundo enlace. El párrafo que habla sobre no deshacerse de HttpClient se refería al diseño anterior. Es fácil confundirlo. Básicamente, con IHttpClientFactory, HttpClient Dispose realmente no hace nada ( stackoverflow.com/a/54326424/476048 ) y los controladores internos son administrados por HttpClientFactory.
Berin Loritsch
83

Funciona más o menos así (ejemplo usando un archivo de imagen / jpg):

async public Task<HttpResponseMessage> UploadImage(string url, byte[] ImageData)
{
    var requestContent = new MultipartFormDataContent(); 
    //    here you can specify boundary if you need---^
    var imageContent = new ByteArrayContent(ImageData);
    imageContent.Headers.ContentType = 
        MediaTypeHeaderValue.Parse("image/jpeg");

    requestContent.Add(imageContent, "image", "image.jpg");

    return await client.PostAsync(url, requestContent);
}

(Puede hacer requestContent.Add()lo que quiera, eche un vistazo al descendiente HttpContent para ver los tipos disponibles para pasar)

Cuando se complete, encontrará el contenido de respuesta dentro HttpResponseMessage.Contentque puede consumir HttpContent.ReadAs*Async.

WDRust
fuente
2
Ahhh gracias por el // here you can specify boundary if you need---^:)
sfarbota
1
¿Por qué esto no funciona? Tarea pública asíncrona <cadena> SendImage (byte [] foto) {var requestContent = new MultipartFormDataContent (); var imageContent = nuevo ByteArrayContent (foto); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse ("image / jpeg"); requestContent.Add (imageContent, "foto", "foto.jpg"); string url = " myAddress / myWS / api / Home / SendImage? foto = "; aguarde _client.PostAsync (url, requestContent); volver "ok"; }
atapi19
1
asyncen la primera línea y awaiten la línea anterior a la última son innecesarios.
1valdis
Para archivos grandes, agregue un contenido continuo a la solicitud en lugar de una matriz de bytes.
Elisabeth
1
@WDRust, con una matriz de bytes, primero carga todo el archivo en la memoria y luego lo envía. Con un contenido continuo, el archivo se lee y se envía utilizando un búfer, que es más eficiente en términos de memoria.
Josef Bláha
53

Este es un ejemplo de cómo publicar cadenas y secuencias de archivos con HTTPClient usando MultipartFormDataContent. La disposición de contenido y el tipo de contenido deben especificarse para cada contenido HTTP:

Aquí está mi ejemplo. Espero eso ayude:

private static void Upload()
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("User-Agent", "CBS Brightcove API Service");

        using (var content = new MultipartFormDataContent())
        {
            var path = @"C:\B2BAssetRoot\files\596086\596086.1.mp4";

            string assetName = Path.GetFileName(path);

            var request = new HTTPBrightCoveRequest()
                {
                    Method = "create_video",
                    Parameters = new Params()
                        {
                            CreateMultipleRenditions = "true",
                            EncodeTo = EncodeTo.Mp4.ToString().ToUpper(),
                            Token = "x8sLalfXacgn-4CzhTBm7uaCxVAPjvKqTf1oXpwLVYYoCkejZUsYtg..",
                            Video = new Video()
                                {
                                    Name = assetName,
                                    ReferenceId = Guid.NewGuid().ToString(),
                                    ShortDescription = assetName
                                }
                        }
                };

            //Content-Disposition: form-data; name="json"
            var stringContent = new StringContent(JsonConvert.SerializeObject(request));
            stringContent.Headers.Add("Content-Disposition", "form-data; name=\"json\"");
            content.Add(stringContent, "json");

            FileStream fs = File.OpenRead(path);

            var streamContent = new StreamContent(fs);
            streamContent.Headers.Add("Content-Type", "application/octet-stream");
            //Content-Disposition: form-data; name="file"; filename="C:\B2BAssetRoot\files\596090\596090.1.mp4";
            streamContent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + Path.GetFileName(path) + "\"");
            content.Add(streamContent, "file", Path.GetFileName(path));

            //content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");

            Task<HttpResponseMessage> message = client.PostAsync("http://api.brightcove.com/services/post", content);

            var input = message.Result.Content.ReadAsStringAsync();
            Console.WriteLine(input.Result);
            Console.Read();
        }
    }
}
Johnny Chu
fuente
11
@Trout ¡No tienes idea de cómo tu código me hizo tan feliz hoy! +1
Pinch
66
Esta es la respuesta completa.
VK
2
Sé que no debemos comentar una nota de agradecimiento. Pero este es el mejor código que he visto sobre cómo usarlo MultipartFormDataContent. Felicitaciones a usted señor
sebagomez
Convenido. Esta es la única respuesta que incluye la cadena y el archivo json como parte del contenido de la carga útil.
frostshoxx
Pruebo en mi computadora (win7 sp1, IIS 7.5) sin Content-Typey Content-Dispositionestá bien, pero en Server 2008 R2 (IIS 7.5) no puedo encontrar archivos, es extraño. Entonces hago como la respuesta.
chengzi
18

Aquí hay otro ejemplo sobre cómo usar HttpClientpara cargar un archivo multipart/form-data.

Carga un archivo en una API REST e incluye el archivo en sí (por ejemplo, un JPG) y parámetros de API adicionales. El archivo se carga directamente desde el disco local a través de FileStream.

Vea aquí el ejemplo completo que incluye lógica específica de API adicional.

public static async Task UploadFileAsync(string token, string path, string channels)
{
    // we need to send a request with multipart/form-data
    var multiForm = new MultipartFormDataContent();

    // add API method parameters
    multiForm.Add(new StringContent(token), "token");
    multiForm.Add(new StringContent(channels), "channels");

    // add file and directly upload it
    FileStream fs = File.OpenRead(path);
    multiForm.Add(new StreamContent(fs), "file", Path.GetFileName(path));

    // send request to API
    var url = "https://slack.com/api/files.upload";
    var response = await client.PostAsync(url, multiForm);
}
Erik Kalkoken
fuente
12

Prueba esto, está funcionando para mí.

private static async Task<object> Upload(string actionUrl)
{
    Image newImage = Image.FromFile(@"Absolute Path of image");
    ImageConverter _imageConverter = new ImageConverter();
    byte[] paramFileStream= (byte[])_imageConverter.ConvertTo(newImage, typeof(byte[]));

    var formContent = new MultipartFormDataContent
    {
        // Send form text values here
        {new StringContent("value1"),"key1"},
        {new StringContent("value2"),"key2" },
        // Send Image Here
        {new StreamContent(new MemoryStream(paramFileStream)),"imagekey","filename.jpg"}
    };

    var myHttpClient = new HttpClient();
    var response = await myHttpClient.PostAsync(actionUrl.ToString(), formContent);
    string stringContent = await response.Content.ReadAsStringAsync();

    return response;
}
Vishnu Kumar
fuente
Perfecto. Exactamente lo que estaba buscando en un TestServer.CreatClient()escenario de .NET Core de una prueba de integración para una carga de datos + archivo.
Vedran Mandić
si el método es HTTPGET, cómo pasar el contenido del formulario
MBG
Las solicitudes GET de @MBG normalmente no tienen un cuerpo de solicitud por convención, por lo que no puede cargar un archivo usando GET (o no, a menos que el servidor al que está enviando sea muy inusual; la mayoría de los servidores web no lo esperarían ni lo admitirían) , porque no hay un cuerpo de solicitud en el que incluir el archivo o los datos del formulario adjunto. Creo que técnicamente no hay nada que impida que esto se haga en teoría, es solo que la convención en casi todas las implementaciones de HTTP es que semánticamente, GET es principalmente para recuperar información (en lugar de enviar) y, por lo tanto, no tiene un cuerpo
ADyson
9

Aquí hay una muestra completa que funcionó para mí. El boundaryvalor en la petición se añade automáticamente por NET.

var url = "http://localhost/api/v1/yourendpointhere";
var filePath = @"C:\path\to\image.jpg";

HttpClient httpClient = new HttpClient();
MultipartFormDataContent form = new MultipartFormDataContent();

FileStream fs = File.OpenRead(filePath);
var streamContent = new StreamContent(fs);

var imageContent = new ByteArrayContent(streamContent.ReadAsByteArrayAsync().Result);
imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");

form.Add(imageContent, "image", Path.GetFileName(filePath));
var response = httpClient.PostAsync(url, form).Result;
nthpixel
fuente
¿Cómo podemos enviar un token con esto? Vea esto por favor: stackoverflow.com/questions/48295877/…
@Softlion - Tengo problemas para NO cargarlo en la memoria antes de enviarlo. Si conoce una mejor manera, publique aquí: stackoverflow.com/questions/52446969/…
emery.noel
1

Ejemplo con preloader Dotnet 3.0 Core

ProgressMessageHandler processMessageHander = new ProgressMessageHandler();

processMessageHander.HttpSendProgress += (s, e) =>
{
    if (e.ProgressPercentage > 0)
    {
        ProgressPercentage = e.ProgressPercentage;
        TotalBytes = e.TotalBytes;
        progressAction?.Invoke(progressFile);
    }
};

using (var client = HttpClientFactory.Create(processMessageHander))
{
    var uri = new Uri(transfer.BackEndUrl);
    client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", AccessToken);

    using (MultipartFormDataContent multiForm = new MultipartFormDataContent())
    {
        multiForm.Add(new StringContent(FileId), "FileId");
        multiForm.Add(new StringContent(FileName), "FileName");
        string hash = "";

        using (MD5 md5Hash = MD5.Create())
        {
            var sb = new StringBuilder();
            foreach (var data in md5Hash.ComputeHash(File.ReadAllBytes(FullName)))
            {
                sb.Append(data.ToString("x2"));
            }
            hash = result.ToString();
        }
        multiForm.Add(new StringContent(hash), "Hash");

        using (FileStream fs = File.OpenRead(FullName))
        {
            multiForm.Add(new StreamContent(fs), "file", Path.GetFileName(FullName));
            var response = await client.PostAsync(uri, multiForm);
            progressFile.Message = response.ToString();

            if (response.IsSuccessStatusCode) {
                progressAction?.Invoke(progressFile);
            } else {
                progressErrorAction?.Invoke(progressFile);
            }
            response.EnsureSuccessStatusCode();
        }
    }
}
D.Oleg
fuente
1
X509Certificate clientKey1 = null;
clientKey1 = new X509Certificate(AppSetting["certificatePath"],
AppSetting["pswd"]);
string url = "https://EndPointAddress";
FileStream fs = File.OpenRead(FilePath);
var streamContent = new StreamContent(fs);

var FileContent = new ByteArrayContent(streamContent.ReadAsByteArrayAsync().Result);
FileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("ContentType");
var handler = new WebRequestHandler();


handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(clientKey1);
handler.ServerCertificateValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) =>
{
    return true;
};


using (var client = new HttpClient(handler))
{
    // Post it
    HttpResponseMessage httpResponseMessage = client.PostAsync(url, FileContent).Result;

    if (!httpResponseMessage.IsSuccessStatusCode)
    {
        string ss = httpResponseMessage.StatusCode.ToString();
    }
}
Rajenthiran T
fuente
Este escenario se usa para cargar archivos en el sitio API con certificado de seguridad
Rajenthiran T
0

Estoy agregando un fragmento de código que muestra cómo publicar un archivo en una API que ha sido expuesta sobre DELETE http verb. Este no es un caso común para cargar un archivo con el verbo http DELETE pero está permitido. Asumí la autenticación de Windows NTLM para autorizar la llamada.

El problema que uno podría enfrentar es que todas las sobrecargas del HttpClient.DeleteAsyncmétodo no tienen parámetros para HttpContentla forma en que lo incorporamos al PostAsyncmétodo.

var requestUri = new Uri("http://UrlOfTheApi");
using (var streamToPost = new MemoryStream("C:\temp.txt"))
using (var fileStreamContent = new StreamContent(streamToPost))
using (var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true })
using (var httpClient = new HttpClient(httpClientHandler, true))
using (var requestMessage = new HttpRequestMessage(HttpMethod.Delete, requestUri))
using (var formDataContent = new MultipartFormDataContent())
{
    formDataContent.Add(fileStreamContent, "myFile", "temp.txt");
    requestMessage.Content = formDataContent;
    var response = httpClient.SendAsync(requestMessage).GetAwaiter().GetResult();

    if (response.IsSuccessStatusCode)
    {
        // File upload was successfull
    }
    else
    {
        var erroResult = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
        throw new Exception("Error on the server : " + erroResult);
    }
}

Necesita los siguientes espacios de nombres en la parte superior de su archivo C #:

using System;
using System.Net;
using System.IO;
using System.Net.Http;

PD: Perdón por usar bloques (patrón IDisposable) en mi código. Desafortunadamente, la sintaxis del uso de la construcción de C # no admite la inicialización de múltiples variables en una sola declaración.

RBT
fuente
-3
public async Task<object> PassImageWithText(IFormFile files)
{
    byte[] data;
    string result = "";
    ByteArrayContent bytes;

    MultipartFormDataContent multiForm = new MultipartFormDataContent();

    try
    {
        using (var client = new HttpClient())
        {
            using (var br = new BinaryReader(files.OpenReadStream()))
            {
                data = br.ReadBytes((int)files.OpenReadStream().Length);
            }

            bytes = new ByteArrayContent(data);
            multiForm.Add(bytes, "files", files.FileName);
            multiForm.Add(new StringContent("value1"), "key1");
            multiForm.Add(new StringContent("value2"), "key2");

            var res = await client.PostAsync(_MEDIA_ADD_IMG_URL, multiForm);
        }
    }
    catch (Exception e)
    {
        throw new Exception(e.ToString());
    }

    return result;
}
Jack el destripador
fuente
Puede mejorar su respuesta comentando el código que escribió
msrd0
OK msrd! Perdón por mi novicio. Intento poner un código claro como "Erik Kalkoke", me encanta. compartiré mi código como recibir imagen por IFormFile en el nodo 1 del servidor y pasar al nodo 2 del servidor aumentando un poco de texto a través de la clase [MultipartFormDataContent] ¡Oh! última línea como esta. resultado = aguardar res.Content.ReadAsStringAsync ();
Jack The Ripper