C#中的HttpClient多部分表单提交

49
我正在尝试使用C#中的HttpClient进行多部分表单提交,但发现以下代码不起作用。

重要提示:


我正在尝试使用 C# 中的 HttpClient 进行多部分表单提交,但是发现以下代码无效。

重要:

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;

完整程序

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

我真的不知道为什么它不起作用。我成功将文件发送到端点,但是请求体(JSON)从未到达。我做错了什么吗?

服务器端代码请求:

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

    }
}

路由配置:

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

}

4
为什么有人给这个投票踩了?我提供了代码,并解释了我试图做什么。这是一个清晰的陈述。 - Khalid Abuhakmeh
1
知道这点很好。我一直以为它是一个WebAPI控制器。 - Daniel Auger
1
жҲ‘з”ҡиҮідёҚзҹҘйҒ“MultipartFormDataContentзҡ„еӯҳеңЁпјҢжүҖд»Ҙж„ҹи°ўжӮЁпјҒ :) - Codecat
你能发布 CourierMessage 的定义吗? - Jay
我的第一个猜测是它与内容类型有关。你的JSON被序列化为多格式内容类型的特定部分,因此只能映射到模型的字符串,而不能反序列化为对象。 - Jay
显示剩余4条评论
4个回答

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

在这里输入图片描述


尝试将一个复杂的列表添加到你的示例中,看看它是否仍然有效。所谓复杂列表,是指 Anchor 对象的列表。我看到你已经将它们删除了...我不认为这是你无意中做的 :) - Khalid Abuhakmeh
@KhalidAbuhakmeh,我没有添加剩余的属性,因为我只是想根据你最初的问题提供一个可行的示例。 - Matija Grcic
1
你可以通过将复杂对象(列表)序列化为逗号分隔列表或JSON blob的方式来发布它们。这仍然是一个不错的解决方案。 - Khalid Abuhakmeh
2
非常感谢。在调用Add()时,在'name'周围添加转义引号正是我一直在寻找的!"\"{0}\"" - Andrew Weddle
转义名称中的引号对我来说是一个很好的提示! - BHuelse
为什么我们需要在名称周围添加引号? - ahong

13

这是使用MultipartFormDataContent在HTTPClient中发布字符串和文件流的示例。需要为每个HTTPContent指定Content-Disposition和Content-Type:

这是我的示例,希望对您有所帮助:

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

1
谢谢。在搜索了好几个小时如何将数据放入请求的http表单后,这个代码片段帮了我大忙:stringContent.Headers.Add("Content-Disposition", "form-data; name="json"");现在我的Json对象非常容易访问。this.Request.Form["json"]; - Talnaci Sergiu Vlad
1
每个HTTPContent都需要指定Content-Disposition和Content-Type。 - Alex Chengalan

6
我发现的问题是,使用MultipartFormDataContent请求消息将始终将请求的内容类型设置为“multipart/form-data”。将json编码并放入请求中只会被模型绑定器视为字符串。
您的选项包括:
  • 使您的mvc操作方法接收一个字符串并反序列化为对象。
  • 将您的模型的每个属性作为表单部分发布。
  • 创建自定义模型绑定器来处理您的请求。
  • 将操作拆分为两个帖子,第一个发送json元数据,另一个发送文件。服务器的响应应该发送一些id或密钥以关联这两个请求。
通过阅读RFC文档MSDN文档,如果您将MultipartFormDataContent替换为MultipartContent,您可能可以做到这一点。但我还没有测试过。

尝试了MultipartContent,但没有起作用。我真的不想采取你的第一个建议,但看起来这可能是情况。 - Khalid Abuhakmeh
你说的关于HttpClient想要将字符串内容作为表单值添加而不仅仅是将其作为帖子的另一部分发布是正确的。不确定这是否是HttpClient中的一个错误或者是我基本理解上的误解。 - Khalid Abuhakmeh
通常情况下,当您需要发布文件时,内容类型为“multipart/form-data”,与任何表单数据类型一样,发布只是一个键值对序列化。当您添加了JSON时,它只是另一对键值对,而不是MVC中模型绑定器中看到的序列化对象。 - Jay
我还意识到另一件让人恼火的事情,那就是我的模型绑定同时发生在URL和主体中。编写模型绑定器意味着这将变得更加困难。 - Khalid Abuhakmeh
我最终编写了一个模型绑定器来处理这个问题。虽然不是很好,但我想只能这样了。 - Khalid Abuhakmeh

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接