C# 中的 HttpClient 多部分表单发布

Posted

技术标签:

【中文标题】C# 中的 HttpClient 多部分表单发布【英文标题】:HttpClient Multipart Form Post in C# 【发布时间】:2013-08-05 13:37:53 【问题描述】:

我正在尝试使用 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 
    );


【问题讨论】:

为什么有人反对这个?我提供了代码,并解释了我想要做什么。这是一个明确的声明。 如果您也显示服务器端代码可能会有所帮助,这样我们就可以看到您尝试阅读它的方式。 服务器端代码只是一个 ASP.NET MVC 端点,模型作为输入类型。那里没有什么壮观的,但我把它放出来表明没有恶作剧。 很高兴知道。我假设它是一个 WebAPI 控制器。 我不知道MultipartFormDataContent 的存在,所以谢谢你! :) 【参考方案1】:
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 对象列表。我看到你删除了它们......我认为这不是你的意外:) @KhalidAbuhakmeh 我没有添加其余属性,因为我只是想为您提供一个基于您最初问题的示例。 以这种方式发布复杂对象(列表)的唯一方法是将它们序列化为逗号分隔的列表或 JSON blob。这仍然是一个很好的解决方案。 非常感谢。在调用 Add() 时在“名称”周围添加转义引号是我一直在寻找的! "\"0\"" 转义名字的引号,对我来说是一个很好的提示!【参考方案2】:

这是一个如何通过 HTTPClient 使用 MultipartFormDataContent 发布字符串和文件流的示例。需要为每个 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();
        
    

【讨论】:

谢谢。经过一个小时的搜索如何将数据放入请求的 http 形式中,这救了我:stringContent.Headers.Add("Content-Disposition", "form-data; name=\"json\"");现在我的 Json 对象非常容易访问。 this.Request.Form["json"]; "需要为每个 HTTPContent 指定 Content-Disposition 和 Content-Type" -> 这救了我【参考方案3】:

所以我看到的问题是MultipartFormDataContent 请求消息将始终将请求的内容类型设置为“multipart/form-data”。结束 json 编码并将其放入请求中,仅“看起来”像模型绑定器作为字符串。

您的选择是:

让您的 mvc 操作方法接收一个字符串并反序列化到您的对象中 将模型的每个属性作为表单部分发布 创建一个自定义模型绑定器来处理您的请求。 把操作分成两个post,第一个发送json元数据,另一个发送文件。来自服务器的响应应该发送一些 id 或 key 来关联这两个请求。

通读the RFC document 和the MSDN documentation,如果您将MultipartFormDataContent 替换为MultipartContent,您或许可以做到这一点。但我还没有测试过。

【讨论】:

尝试了 MultipartContent,但没有成功。我真的不想做你的第一个建议,但看起来更有可能是这样。 您说得对,HttpClient 希望将字符串内容添加为表单值,而不是仅仅将其作为帖子的另一部分发布。不确定这是 HttpClient 中的错误还是我的基本误解。 嗯,通常当您必须发布文件时,内容类型是“multipart/form-data”,与任何表单数据类型一样,发布只是键值对序列化。当您添加 json 时,它只是另一对,不被视为 MVC 中模型绑定器的序列化对象。 我还意识到了其他一些糟糕的事情,我有一个从 URL 和正文发生的模型绑定的组合。编写模型绑定器意味着这样做会更加困难。 我最终编写了一个模型活页夹来处理这个问题。不是很好,但我猜必须这样做。【参考方案4】:
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;

【讨论】:

注意:文件大小会有限制(可在配置文件中调整)

以上是关于C# 中的 HttpClient 多部分表单发布的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Jetty HttpClient 中进行多部分/表单数据发布

Apache HttpClient 制作多部分表单帖子

Java 9 HttpClient 发送多部分/表单数据请求

使用带有表单编码参数和标头的 C# httpclient 发布

如何在 C# 中使用 HttpClient 发送文件和表单数据

来自 C# 客户端的多部分表单