197

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.

derekerdmann
  • 17,696
  • 11
  • 76
  • 110
ident
  • 3,885
  • 3
  • 14
  • 8
  • 1
    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. – ident May 07 '13 at 10:27
  • You can look this post answer. (With Proxy settings) https://stackoverflow.com/a/50462636/2123797 – Ergin Çelik May 22 '18 at 08:04

10 Answers10

189

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;
              }
          }
     }
}
Shawn
  • 2,356
  • 6
  • 48
  • 82
ident
  • 3,885
  • 3
  • 14
  • 8
  • 7
    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. – Léon Pelletier Jun 25 '13 at 08:50
  • 1
    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. – Dunc Sep 04 '17 at 12:41
  • 8
    @MauricioAviles, your link is broken. I found this one which explained it nicely: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ – Kevin Harker Aug 11 '18 at 18:05
  • 2
    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). – jhhwilliams May 24 '19 at 08:13
  • @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. – Berin Loritsch Jul 10 '19 at 23:01
  • @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](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#consumption-patterns) and [This one says not to dispose](https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) – Kevin Harker Jul 12 '19 at 17:51
  • 1
    @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://stackoverflow.com/a/54326424/476048) and the internal handlers are managed by the HttpClientFactory. – Berin Loritsch Jul 15 '19 at 12:43
  • 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. – Kevin Harker Jul 15 '19 at 18:13
  • 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. – Tim Jan 20 '22 at 02:21
  • `using (var client = new HttpClient())` is wrong. HttpClient is reentrant. There should be one (static) instance for the whole app. – JHBonarius Mar 04 '22 at 13:11
104

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.

WDRust
  • 3,663
  • 1
  • 19
  • 23
  • 2
    Ahhh thanks for the `// here you can specify boundary if you need---^` :) – sfarbota Sep 10 '15 at 07:14
  • 1
    why this not works? public async Task 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 = "http://myAddress/myWS/api/Home/SendImage?foto="; await _client.PostAsync(url, requestContent); return "ok"; } – atapi19 Jan 29 '18 at 14:59
  • 1
    `async` on the first line and `await` on the line before the last are unnecessary. – 1valdis Apr 02 '18 at 23:08
  • For large files, add a stream content to the request rather than a byte array. – Eli Jun 21 '18 at 20:32
  • can you expand on why that would be useful @Elisabeth – WDRust Jul 05 '18 at 01:32
  • 1
    @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. – Josef Bláha May 27 '19 at 17:54
66

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();
        }
    }
}
Steve Trout
  • 9,261
  • 2
  • 19
  • 30
Johnny Chu
  • 899
  • 7
  • 4
  • 11
    @Trout You have no idea how your code made me sooo happy today! +1 – Pinch Jun 01 '15 at 18:41
  • 6
    This is the complete the answer. – V K Sep 13 '16 at 10:37
  • 2
    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 sir – sebagomez Oct 04 '16 at 15:24
  • 1
    Agreed. This is the only answer that include json string and file as part of the payload content. – frostshoxx Feb 27 '18 at 22:34
  • 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. – chengzi Mar 28 '18 at 01:35
  • This saved my day.. exactly what i needed! Kudos!!!! Thank you for your effort @Johnny Chu – Afzal Ali Sep 02 '19 at 07:38
  • What is HTTPBrightCoveRequest? Is that a third party tool? – NealWalters Feb 28 '23 at 15:37
35

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;
}
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
Vishnu Kumar
  • 397
  • 4
  • 6
  • 2
    Flawless. Exactly what I was looking for in a .NET Core `TestServer.CreatClient()` scenario of an integration test for a data + file upload. – Vedran Mandić Nov 20 '19 at 09:51
  • if the method is HTTPGET how to pass formcontent – dev Jul 09 '20 at 15:50
  • 1
    @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 body – ADyson Jul 17 '20 at 13:58
  • .Net 5 - you simple solution perfectly works for me! – Zi Cold Jan 17 '22 at 10:12
  • Perfect! Works for me. Most API, the ones I've encountered, require all 3 parameters to accept new StreamContent. – Peter Chikov Mar 04 '22 at 23:33
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);
}
Erik Kalkoken
  • 30,467
  • 8
  • 79
  • 114
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;
nthpixel
  • 3,041
  • 3
  • 30
  • 42
  • How can we send a token with this? See this please: https://stackoverflow.com/questions/48295877/webclient-too-many-automatic-redirections-were-attempted –  Jan 17 '18 at 15:36
  • @Softlion - I am having trouble NOT loading it into memory before sending. If you know a better way, please post here: https://stackoverflow.com/questions/52446969/c-sharp-streamcontent-and-file-openread-not-producing-httpable-multipart-cont – emery.noel Sep 21 '18 at 17:09
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.

RBT
  • 24,161
  • 21
  • 159
  • 240
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();
        }
    }
}
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
D.Oleg
  • 71
  • 1
  • 3
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();
    }
}
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
Rajenthiran T
  • 77
  • 1
  • 6
-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;
}
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
  • You could improve your answer by commenting on the code that you wrote – msrd0 Aug 11 '19 at 15:35
  • 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(); – Jack The Ripper Aug 12 '19 at 07:06
  • Good solution, nevertheless. +1 – Peter Chikov Mar 04 '22 at 23:34