C# HttpClient 4.5 multipart/form-data upload
Asked Answered
H

10

200

Does anyone know how to use the HttpClient in .Net 4.5 with multipart/form-data upload?

I couldn't find any examples on the internet.

Herriott answered 7/5, 2013 at 10:19 Comment(2)
I try'd but i haven't any idea how to start it.. where i add the byteArray to the content and so on. i need kind of a start help.Herriott
You can look this post answer. (With Proxy settings) https://mcmap.net/q/129670/-post-multipart-form-data-in-c-httpclient-4-5Allophane
H
191

my result looks like this:

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;
              }
          }
     }
}
Herriott answered 16/5, 2013 at 19:35 Comment(10)
Wow, it's so much simpler to do this when uploading big files to REST API. I don't like to comment for thanks, but thanks. It's portable for Windows Phone 8.Lorin
This code failed for me as the boundary string passed to new MultipartFormDataContent(...) contained an invalid boundary character (maybe the "/" separator). No errors, just no files posted into the server - in my case, Context.Request.Files.Count = 0 in API controller. Possibly just a Nancy issue, but I suggest using something like DateTime.Now.Ticks.ToString("x") instead.Rattling
@MauricioAviles, your link is broken. I found this one which explained it nicely: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrongLdopa
If you get an error: "Uploaded file(s) not found" try to add the key and fileName parameters to content (bilddatei and upload.jpg in this example).Unguarded
@KevinHarker, With the HttpClientFactory available, it's safe to work with the using statement now. There are other errors that come with having one global instance of HttpClient.Photofinishing
@BerinLoritsch Do you have an article mentioning that? I'm still seeing references saying not to dispose of the HttpClient. This one specifically is not disposing but is also not using static and This one says not to disposeLdopa
@KevinHarker, Reread that second link. The paragraph talking about not disposing HttpClient was referring to the previous design. It's easy to confuse. Basically, with the IHttpClientFactory, the HttpClient Dispose doesn't really do anything (https://mcmap.net/q/129671/-should-httpclient-instances-created-by-httpclientfactory-be-disposed) and the internal handlers are managed by the HttpClientFactory.Photofinishing
I think the point from those articles is that you're not really supposed to wrap the HttpClient in a using statement like the answer above. Both of my recent links state that, and the example in the article you linked did the same. The only difference seems that you shouldn't keep a static object around and should instead let the object dispose as you would any other object when using IHttpClientFactory. Thanks @BerinLoritsch! We should probably put in a new answer using IHttpClientFactory correctly since it improves things quite a bit.Ldopa
So initializing with the Boundary argument was what I was missing; I had otherwise been doing this but had no boundary, so my requests uniformly failed. Now my process is working thanks to that addition.Ichthyology
using (var client = new HttpClient()) is wrong. HttpClient is reentrant. There should be one (static) instance for the whole app.Hydrography
S
106

It works more or less like this (example using an image/jpg file):

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

(You can requestContent.Add() whatever you want, take a look at the HttpContent descendant to see available types to pass in)

When completed, you'll find the response content inside HttpResponseMessage.Content that you can consume with HttpContent.ReadAs*Async.

Scever answered 15/5, 2013 at 11:3 Comment(6)
Ahhh thanks for the // here you can specify boundary if you need---^ :)Conservatism
why this not works? public async Task<string> SendImage(byte[] foto) { var requestContent = new MultipartFormDataContent(); var imageContent = new ByteArrayContent(foto); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg"); requestContent.Add(imageContent, "foto", "foto.jpg"); string url = "myAddress/myWS/api/Home/SendImage?foto="; await _client.PostAsync(url, requestContent); return "ok"; }Marguritemargy
async on the first line and await on the line before the last are unnecessary.Nildanile
For large files, add a stream content to the request rather than a byte array.Tuberculin
can you expand on why that would be useful @ElisabethScever
@WDRust, with a byte array, you first load the whole file to memory and then send it. With a stream content, the file is read and sent using a buffer, which is more efficient in terms of memory.Pneuma
A
67

This is an example of how to post string and file stream with HTTPClient using MultipartFormDataContent. The Content-Disposition and Content-Type need to be specified for each HTTPContent:

Here's my example. Hope it helps:

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();
        }
    }
}
Attis answered 30/1, 2015 at 18:36 Comment(9)
@Trout You have no idea how your code made me sooo happy today! +1Suboceanic
This is the complete the answer.Motivate
I know we're not supposed to comment a thank you note. But this right here is the best code I've seen on how to use MultipartFormDataContent. Kudos to you sirCrumhorn
Agreed. This is the only answer that include json string and file as part of the payload content.Viki
I test on my computer(win7 sp1, IIS 7.5) without Content-Type and Content-Disposition is Ok, but on Server 2008 R2(IIS 7.5) can't find files, it's strange. So I do as the answer.Negligence
This saved my day.. exactly what i needed! Kudos!!!! Thank you for your effort @Johnny ChuVitrescent
What is HTTPBrightCoveRequest? Is that a third party tool?Magnetostriction
@Magnetostriction yes, HTTPBrightCoveRequest is a custom data structure that forms part of a third-party service called BrightCove, an online video streaming platform. It is not required. Just here for demonstration purposes.Scurlock
This answer is good, because unlike others, it uses a FileStream, instead MemoryStream or byte[] arrays, which could quickly consume a lot of memory when the file content size is quite large (GB ranges).Scurlock
S
37

Try this its working for me.

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;
}
Samos answered 7/11, 2018 at 13:21 Comment(5)
Flawless. Exactly what I was looking for in a .NET Core TestServer.CreatClient() scenario of an integration test for a data + file upload.Lindholm
if the method is HTTPGET how to pass formcontentReadjustment
@MBG GET requests don't normally have a request body by convention, so you can't upload a file using GET (or not unless the server you're sending to is very unusual - most webservers would not expect it or support it), because there's no request body in which to include either the file or the accompanying form data. I believe that technically there's nothing which would prevent this from being done in theory, it's just that the convention across almost all implementations of HTTP is that semantically, GET is primarily for retrieving information (rather than sending) and so does not have a bodyArly
.Net 5 - you simple solution perfectly works for me!Kamerun
Perfect! Works for me. Most API, the ones I've encountered, require all 3 parameters to accept new StreamContent.Megen
H
34

Here is another example on how to use HttpClient to upload a multipart/form-data.

It uploads a file to a REST API and includes the file itself (e.g. a JPG) and additional API parameters. The file is directly uploaded from local disk via FileStream.

See here for the full example including additional API specific logic.

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);
}
Hunnicutt answered 13/11, 2018 at 15:57 Comment(0)
B
13

Here's a complete sample that worked for me. The boundary value in the request is added automatically by .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;
Betook answered 8/5, 2017 at 20:31 Comment(2)
How can we send a token with this? See this please: #48296377Quasar
@Softlion - I am having trouble NOT loading it into memory before sending. If you know a better way, please post here: #52447469Pericline
V
1

I'm adding a code snippet which shows on how to post a file to an API which has been exposed over DELETE http verb. This is not a common case to upload a file with DELETE http verb but it is allowed. I've assumed Windows NTLM authentication for authorizing the call.

The problem that one might face is that all the overloads of HttpClient.DeleteAsync method have no parameters for HttpContent the way we get it in PostAsync method

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

You need below namespaces at the top of your C# file:

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

P.S. You are seeing a number of using blocks(IDisposable pattern) in the above code snippet which doesn't look very clean. Unfortunately, the syntax of using construct doesn't support initializing multiple variables in single statement.

Vulnerary answered 28/9, 2019 at 9:13 Comment(0)
A
1

Example with 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();
        }
    }
}
Armin answered 12/12, 2019 at 16:10 Comment(0)
G
0
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();
    }
}
Gendron answered 20/9, 2019 at 12:47 Comment(1)
This scenario is use for file upload in to API site with security certificateGendron
T
-5
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;
}
Tarryn answered 11/8, 2019 at 15:10 Comment(3)
You could improve your answer by commenting on the code that you wroteAbysmal
OK msrd ! Sorry about my novice. I try to put a clear code like "Erik Kalkoke", i love it. i'll share my code like receive image by IFormFile at server node 1 and pass to server node 2 by increase some text via class [MultipartFormDataContent] Oh ! last line like this. result = await res.Content.ReadAsStringAsync();Tarryn
Good solution, nevertheless. +1Megen

© 2022 - 2024 — McMap. All rights reserved.