Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient Multipart Form Post in C#

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 = "[email protected]",
                        To = new[] { "[email protected]", "[email protected]" },
                        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 }
    );

}
like image 587
Khalid Abuhakmeh Avatar asked Aug 05 '13 13:08

Khalid Abuhakmeh


3 Answers

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", "[email protected]")
                 //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

like image 182
Matija Grcic Avatar answered Nov 18 '22 23:11

Matija Grcic


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();
        }
    }
}
like image 27
Johnny Chu Avatar answered Nov 19 '22 01:11

Johnny Chu


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.

like image 6
Jay Avatar answered Nov 19 '22 00:11

Jay