C# HttpClient 4.5多部分/form-data上传

199

请问有谁知道如何在 .Net 4.5 中使用 HttpClient 实现 multipart/form-data 上传吗?

我在互联网上找不到任何示例。


1
我尝试过,但是我不知道如何开始...我应该在哪里将byteArray添加到内容中等等。我需要一些起步帮助。 - ident
你可以查看这篇帖子的答案。(使用代理设置)https://dev59.com/m1UM5IYBdhLWcg3wKNuB#50462636 - Ergin Çelik
10个回答

189

我的结果看起来像这样:

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

7
上传大文件到REST API变得更加简单了,哇!我不喜欢只留下感谢的评论,但还是要说声谢谢。这个方法适用于Windows Phone 8。 - Léon Pelletier
1
这段代码对我来说失败了,因为传递给 new MultipartFormDataContent(...) 的边界字符串包含一个无效的边界字符(可能是“/”分隔符)。没有错误,只是没有文件发布到服务器上 - 在我的情况下,API控制器中的 Context.Request.Files.Count = 0。可能只是一个 Nancy 问题,但我建议改用类似于 DateTime.Now.Ticks.ToString("x") 的东西。 - Dunc
8
@MauricioAviles,你的链接失效了。我找到了这个可以很好地解释它:https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ - Kevin Harker
3
如果你遇到了错误:“未找到上传的文件”,请尝试将keyfileName参数添加到content中(在本例中为bilddateiupload.jpg)。 - jhhwilliams
1
@KevinHarker,请重新阅读第二个链接。提到不要处理HttpClient的段落是指先前的设计。很容易混淆。基本上,使用IHttpClientFactory时,HttpClient Dispose实际上并没有做任何事情(https://dev59.com/RlUL5IYBdhLWcg3wDkdT#54326424),而内部处理程序由HttpClientFactory管理。 - Berin Loritsch
显示剩余5条评论

105

它大致是这样工作的(使用图像/jpg文件的示例):

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

你可以使用requestContent.Add()添加任何想要的东西,查看HttpContent派生类以了解可用的传递类型。

完成后,您将在HttpResponseMessage.Content中找到响应内容,您可以使用HttpContent.ReadAs*Async进行消耗。


2
啊,谢谢你的 // here you can specify boundary if you need---^ :) - sfarbota
1
为什么这个不工作?public async Task<string> 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
1
第一行的“async”和倒数第二行的“await”是不必要的。 - 1valdis
对于大文件,请将流内容添加到请求中,而不是字节数组。 - Eli
能否详细说明一下为什么这会有用,@Elisabeth? - WDRust
1
@WDRust,使用字节数组时,您需要先将整个文件加载到内存中,然后再发送。而使用流内容时,文件会被读取并使用缓冲区进行发送,这在内存方面更加高效。 - Josef Bláha

66

这是使用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");
            //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();
        }
    }
}

11
@Trout,你不知道你的代码今天让我多开心啊!+1 - Pinch
6
这是完整的答案。 - V K
2
我知道我们不应该在注释中写感谢的话。但是这里所展示的关于如何使用 MultipartFormDataContent 的代码是我见过最好的。向您致敬! - sebagomez
1
同意。这是唯一一个将JSON字符串和文件作为有效载荷内容的答案。 - frostshoxx
HTTPBrightCoveRequest是什么?它是第三方工具吗? - NealWalters
显示剩余2条评论

36

试试这个方法,对我来说有效。

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

2
完美。这正是我在数据+文件上传的集成测试中,针对.NET Core TestServer.CreatClient() 场景所期望的结果。 - Vedran Mandić
如果方法是HTTPGET,如何传递表单内容。 - dev
1
@MBG 根据惯例,GET请求通常不具有请求正文,因此您无法使用GET上传文件(除非您发送到的服务器非常不寻常-大多数Web服务器不会期望或支持它),因为没有请求正文可以包含文件或随附的表单数据。我认为从技术上讲,在理论上没有什么可以阻止这样做,只是几乎所有HTTP实现的惯例是,语义上,GET主要用于检索信息(而不是发送),因此没有请求正文。 - ADyson
.Net 5 - 你的简单解决方案完美地适用于我! - Zi Cold
太好了!对我有用。大多数API(我遇到的那些)需要所有3个参数才能接受新的StreamContent。 - Peter Chikov

34

这里是使用 HttpClient 上传 multipart/form-data 的另一个示例。

它将文件(例如 JPG)和其他 API 参数一起上传到 REST API,并直接从本地磁盘通过 FileStream 上传文件本身。

请参见此处,包括其他API特定逻辑在内的完整示例。

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

13

这是适合我的完整示例。请求中的boundary值由.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;

我们如何在此发送一个令牌?请参见以下链接:https://stackoverflow.com/questions/48295877/webclient-too-many-automatic-redirections-were-attempted - user9046719
@Softlion - 我在发送之前无法避免将其加载到内存中。如果您知道更好的方法,请在此处发布:https://stackoverflow.com/questions/52446969/c-sharp-streamcontent-and-file-openread-not-producing-httpable-multipart-cont - emery.noel

1
我正在添加一个代码片段,展示如何将文件发布到通过DELETE http动词公开的API。这不是上传文件的常见情况,但是它是允许的。我假设使用Windows NTLM身份验证来授权调用。
可能会遇到的问题是,HttpClient.DeleteAsync方法的所有重载都没有HttpContent参数,就像我们在PostAsync方法中获取的那样。
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);
    }
}

您需要在C#文件的顶部使用以下命名空间:

using System;
using System.Net;
using System.IO;
using System.Net.Http;
附言:您在上面的代码片段中看到了使用块(IDisposable模式)的数量,这看起来不太干净。不幸的是,using结构的语法不支持在单个语句中初始化多个变量。

1

带有预加载程序的示例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();
        }
    }
}

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

这种情况用于通过安全证书将文件上传到API站点。 - Rajenthiran T

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

你可以通过对自己编写的代码进行评论来改进回答。 - msrd0
好的,msrd!对不起,我是个新手。 我试着像“Erik Kalkoke”一样写出清晰的代码,我很喜欢它。 我将分享我的代码,例如通过类[MultipartFormDataContent]增加一些文本,在服务器节点1上接收图像并传递到服务器节点2。哦!最后一行是这样的。result = await res.Content.ReadAsStringAsync(); - Jack The Ripper
不过,很棒的解决方案。+1 - Peter Chikov

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