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

标签 c# http multipartform-data

我正在尝试通过 C# 客户端(Outlook 插件)在 php 应用程序中填写表单。我使用 Fiddler 从 php 应用程序中查看原始请求,并且表单作为 multipart/form 传输。不幸的是,.Net 没有对这种类型的表单提供原生支持(WebClient 只有一种上传文件的方法)。 有没有人知道图书馆或有一些代码来实现这个?我想发布不同的值,另外(但只是有时)一个文件。

感谢您的帮助, 塞巴斯蒂安

最佳答案

谢谢大家的回答!我最近不得不让它工作,并大量使用了你的建议。但是,有几个棘手的部分没有按预期工作,主要与实际包含文件有关(这是问题的重要部分)。这里已经有很多答案,但我认为这可能对将来的某个人有用(我在网上找不到很多明确的例子)。我 wrote a blog post这就解释得更多了。

基本上,我首先尝试将文件数据作为 UTF8 编码字符串传递,但我在编码文件时遇到了问题(对于纯文本文件它工作正常,但是在上传 Word 文档时,例如,如果我尝试使用 Request.Files[0].SaveAs() 保存传递到已发布表单的文件,但在 Word 中打开文件无法正常工作。我发现如果直接使用 Stream(而不是比 StringBuilder),它按预期工作。另外,我做了一些修改,使我更容易理解。

顺便说一句,Multipart Forms Request for CommentsW3C Recommendation for mulitpart/form-data有一些有用的资源,以防任何人需要规范引用。

我将 WebHelpers 类更改为更小一些并具有更简单的接口(interface),它现在称为 FormUpload。如果传递 FormUpload.FileParameter,则可以传递 byte[] 内容以及文件名和内容类型,如果传递字符串,它会将其视为标准名称/值组合。

这是 FormUpload 类:

// Implements multipart/form-data POST in C# http://www.ietf.org/rfc/rfc2388.txt
// http://www.briangrinstead.com/blog/multipart-form-post-in-c
public static class FormUpload
{
    private static readonly Encoding encoding = Encoding.UTF8;
    public static HttpWebResponse MultipartFormDataPost(string postUrl, string userAgent, Dictionary<string, object> postParameters)
    {
        string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
        string contentType = "multipart/form-data; boundary=" + formDataBoundary;

        byte[] formData = GetMultipartFormData(postParameters, formDataBoundary);

        return PostForm(postUrl, userAgent, contentType, formData);
    }
    private static HttpWebResponse PostForm(string postUrl, string userAgent, string contentType, byte[] formData)
    {
        HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest;

        if (request == null)
        {
            throw new NullReferenceException("request is not a http request");
        }

        // Set up the request properties.
        request.Method = "POST";
        request.ContentType = contentType;
        request.UserAgent = userAgent;
        request.CookieContainer = new CookieContainer();
        request.ContentLength = formData.Length;

        // You could add authentication here as well if needed:
        // request.PreAuthenticate = true;
        // request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
        // request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("username" + ":" + "password")));

        // Send the form data to the request.
        using (Stream requestStream = request.GetRequestStream())
        {
            requestStream.Write(formData, 0, formData.Length);
            requestStream.Close();
        }

        return request.GetResponse() as HttpWebResponse;
    }

    private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary)
    {
        Stream formDataStream = new System.IO.MemoryStream();
        bool needsCLRF = false;

        foreach (var param in postParameters)
        {
            // Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
            // Skip it on the first parameter, add it to subsequent parameters.
            if (needsCLRF)
                formDataStream.Write(encoding.GetBytes("\r\n"), 0, encoding.GetByteCount("\r\n"));

            needsCLRF = true;

            if (param.Value is FileParameter)
            {
                FileParameter fileToUpload = (FileParameter)param.Value;

                // Add just the first part of this param, since we will write the file data directly to the Stream
                string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n",
                    boundary,
                    param.Key,
                    fileToUpload.FileName ?? param.Key,
                    fileToUpload.ContentType ?? "application/octet-stream");

                formDataStream.Write(encoding.GetBytes(header), 0, encoding.GetByteCount(header));

                // Write the file data directly to the Stream, rather than serializing it to a string.
                formDataStream.Write(fileToUpload.File, 0, fileToUpload.File.Length);
            }
            else
            {
                string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
                    boundary,
                    param.Key,
                    param.Value);
                formDataStream.Write(encoding.GetBytes(postData), 0, encoding.GetByteCount(postData));
            }
        }

        // Add the end of the request.  Start with a newline
        string footer = "\r\n--" + boundary + "--\r\n";
        formDataStream.Write(encoding.GetBytes(footer), 0, encoding.GetByteCount(footer));

        // Dump the Stream into a byte[]
        formDataStream.Position = 0;
        byte[] formData = new byte[formDataStream.Length];
        formDataStream.Read(formData, 0, formData.Length);
        formDataStream.Close();

        return formData;
    }

    public class FileParameter
    {
        public byte[] File { get; set; }
        public string FileName { get; set; }
        public string ContentType { get; set; }
        public FileParameter(byte[] file) : this(file, null) { }
        public FileParameter(byte[] file, string filename) : this(file, filename, null) { }
        public FileParameter(byte[] file, string filename, string contenttype)
        {
            File = file;
            FileName = filename;
            ContentType = contenttype;
        }
    }
}

这是调用代码,它上传了一个文件和一些正常的发布参数:

// Read file data
FileStream fs = new FileStream("c:\\people.doc", FileMode.Open, FileAccess.Read);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, data.Length);
fs.Close();

// Generate post objects
Dictionary<string, object> postParameters = new Dictionary<string, object>();
postParameters.Add("filename", "People.doc");
postParameters.Add("fileformat", "doc");
postParameters.Add("file", new FormUpload.FileParameter(data, "People.doc", "application/msword"));

// Create request and receive response
string postURL = "http://localhost";
string userAgent = "Someone";
HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters);

// Process response
StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
string fullResponse = responseReader.ReadToEnd();
webResponse.Close();
Response.Write(fullResponse);

关于c# - 来自 C# 客户端的多部分表单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/219827/

相关文章:

c# - 什么是 LINQ,它有什么作用?

c# - 对从线程触发的事件进行单元测试

android - 如何取消 Android http 请求?

c# - 如何知道该电子邮件已发送,并且可以删除附件?

c# - 将 JSON 反序列化为在局部 View 中呈现的模型(restsharp 或任何其他方法)

apache - 如何使用 lua 获取 HTTP 响应体?

java - Apache Http 组件 - 设置 cookie

Java : InputStream to Multi-part file conversion, 结果文件为空

java - Raw Spring 5 和 Commons FileUpload 不起作用

java - 如何从 RequestContextHolder 获取 MultipartHttpServletRequest?