49

I'm trying to do a multipart form post using the HttpClient in C# and am finding the following code does not work.

Important:

var jsonToSend = JsonConvert.SerializeObject(json, Formatting.None, new IsoDateTimeConverter());
var multipart = new MultipartFormDataContent();
var body = new StringContent(jsonToSend, Encoding.UTF8, "application/json");

multipart.Add(body);
multipart.Add(new ByteArrayContent(File.ReadAllBytes("test.txt")), "test", "test.txt");

var httpClient = new HttpClient();
var response = httpClient.PostAsync(new Uri("http://localhost:55530"), multipart).Result;

Full Program :

namespace CourierMvc.Worker
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("Hit any key to make request.");
                Console.ReadKey();

                try
                {
                    var request = new RestRequest(Method.POST)
                    {
                        Resource = "http://localhost:55530"
                    };

                    var json = new CourierMessage
                    {
                        Id = Guid.NewGuid().ToString(),
                        Key = "awesome",
                        From = "khalid@home.com",
                        To = new[] { "me@test.com", "you@test.com" },
                        Subject = "test",
                        Body = "body",
                        Processed = DateTimeOffset.UtcNow,
                        Received = DateTime.Now,
                        Created = DateTime.Now,
                        Sent = DateTime.Now,
                        Links = new[] { new Anchor { Link = "http://google.com" }, new Anchor { Link = "http://yahoo.com" } }
                    };

                    var jsonToSend = JsonConvert.SerializeObject(json, Formatting.None, new IsoDateTimeConverter());
                    var multipart = new MultipartFormDataContent();
                    var body = new StringContent(jsonToSend, Encoding.UTF8, "application/json");

                    multipart.Add(body);
                    multipart.Add(new ByteArrayContent(File.ReadAllBytes("test.txt")), "test", "test.txt");

                    var httpClient = new HttpClient();
                    var response = httpClient.PostAsync(new Uri("http://localhost:55530"), multipart).Result;

                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}

I really have no idea why it doesn't work. I get the file to post to the endpoint, but the body (json) never gets there. Am I doing something wrong?

Server Side Code Request:

namespace CourierMvc.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return Content("Home#Index");
        }


        [ValidateInput(false)]
        public ActionResult Create(CourierMessage input)
        {
            var files = Request.Files;

            return Content("OK");
        }

    }
}

Route Config:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Create", id = UrlParameter.Optional }
    );

}
Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75

4 Answers4

58
public class CourierMessage
{
    public string Id { get; set; }
    public string Key { get; set; }
    public string From { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public DateTimeOffset Processed { get; set; }
    public DateTime Received { get; set; }
    public DateTime Created { get; set; }
    public DateTime Sent { get; set; }
    public HttpPostedFileBase File { get; set; }
}  




while (true)
{
    Console.WriteLine("Hit any key to make request.");
    Console.ReadKey();

    using (var client = new HttpClient())
    {
        using (var multipartFormDataContent = new MultipartFormDataContent())
        {
            var values = new[]
            {
                new KeyValuePair<string, string>("Id", Guid.NewGuid().ToString()),
                new KeyValuePair<string, string>("Key", "awesome"),
                new KeyValuePair<string, string>("From", "khalid@home.com")
                 //other values
            };

            foreach (var keyValuePair in values)
            {
                multipartFormDataContent.Add(new StringContent(keyValuePair.Value), 
                    String.Format("\"{0}\"", keyValuePair.Key));
            }

            multipartFormDataContent.Add(new ByteArrayContent(File.ReadAllBytes("test.txt")), 
                '"' + "File" + '"', 
                '"' + "test.txt" + '"');

            var requestUri = "http://localhost:5949";
            var result = client.PostAsync(requestUri, multipartFormDataContent).Result;
        }
    }
}  

enter image description here

Matija Grcic
  • 12,963
  • 6
  • 62
  • 90
  • Try adding a complex list to your example and see if it still works. By a complex list, I mean the List of Anchor objects. I see that you removed them... I don't think that was an accident on your part :) – Khalid Abuhakmeh Aug 05 '13 at 23:56
  • @KhalidAbuhakmeh I didn't add the remaining properties as i just wanted to provide you with a example that's working based on your initial question. – Matija Grcic Aug 12 '13 at 11:38
  • 1
    The only way you can Post a complex object (List) this way is if you serialize them into a comma separated list or into a JSON blob. This is still a good solution. – Khalid Abuhakmeh Aug 14 '13 at 12:27
  • 2
    Thank you so much. Adding escaped quotes around the 'name' when calling Add() was what I've been looking for! `"\"{0}\""` – Andrew Weddle Oct 07 '15 at 01:47
  • Escaping the quotes of the name, was a great hint for me! – BHuelse Jul 20 '17 at 14:29
  • why do we need to add quotes around the name? – ahong Dec 20 '19 at 05:40
13

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");
            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();
        }
    }
}
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
Johnny Chu
  • 899
  • 7
  • 4
  • 1
    Thank you. This saved me after a good hour of searching how to put data in the http form of the request: stringContent.Headers.Add("Content-Disposition", "form-data; name=\"json\""); Now my Json object is super easy to acces. this.Request.Form["json"]; – Talnaci Sergiu Vlad May 19 '17 at 10:08
  • 1
    "The Content-Disposition and Content-Type need to be specified for each HTTPContent" -> this saved me – Alex Chengalan Jun 13 '17 at 09:54
6

So the problem I'm seeing is that the MultipartFormDataContent request message will always set the content type of the request to "multipart/form-data". Endcoding json and placing that into the request only "looks" like to the model binder as a string.

Your options are:

  • have your mvc action method receive a string and deserialize into your object
  • post each property of your model as a form part
  • create a custom model binder that will handle your request.
  • Breakup the operation into two posts, first sends the json metadata, the other sends the file. The response from the server should send some id or key to correlate the two requests.

Reading through the RFC document and the MSDN documentation you may be able to do this, if you replace MultipartFormDataContent with MultipartContent. But I have not tested this yet.

Jay
  • 6,224
  • 4
  • 20
  • 23
  • Tried MultipartContent and that did not work. I really don't want to do your first suggestion, but it is looking more likely that it will be the case. – Khalid Abuhakmeh Aug 05 '13 at 14:52
  • You are right about the HttpClient wanting to add string content as a form value instead of just posting it as another part of the post. Not sure if this is a bug in HttpClient or a fundamental misunderstanding on my part. – Khalid Abuhakmeh Aug 05 '13 at 14:59
  • Well, normally when you have to post a file the content type is 'multipart/form-data', as with any form data type the post is simply a key value pair serialization. When you added your json, it was just another pair, not seen as a serialized object to the model binder in MVC. – Jay Aug 05 '13 at 15:03
  • I also realized something else that sucks about this, I have a combination of model binding that happens from both the URL and the body. Writing a modelbinder means doing that will be that much harder. – Khalid Abuhakmeh Aug 05 '13 at 16:03
  • I ended up writing a model binder to handle this. Not great, but will have to do I guess. – Khalid Abuhakmeh Aug 05 '13 at 17:34
0
string path = @"C:\New folder\Test.pdf";  // **ANY FILE**
                            
var formContent = new MultipartFormDataContent
{
     { new ByteArrayContent(File.ReadAllBytes(path)), "file", Path.GetFileName(path) }
};

var client = new HttpClient();
var response = client.PostAsync(_configuration["Url"], formContent).Result;
Rahul Uttarkar
  • 3,367
  • 3
  • 35
  • 40