当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《来自C#客户端的多部分表单》经验,为你挑选了5个好方法。

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

谢谢你的帮助,塞巴斯蒂安



1> Brian Grinst..:

感谢大家的答案!我最近不得不让它工作,并大量使用你的建议.然而,有一些棘手的部分没有按预期工作,主要是与实际包括文件(这是问题的一个重要部分)有关.这里有很多答案,但我认为这可能对将来的某些人有用(我在网上找不到很多明确的例子).我写了一篇博文,解释了一下.

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

顺便说一下,如果任何人需要参考规范,那么Multipart Forms for Comments和W3C关于mulitpart/form-data的建议是一些有用的资源.

我将WebHelpers类更改为更小并且具有更简单的接口,现在调用它FormUpload.如果传递a 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 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 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;
        }
    }
}

这是调用代码,它上传文件和一些普通的post参数:

// 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 postParameters = new Dictionary();
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);



2> dnolan..:

这是从我写的一些示例代码中剪切和粘贴的,希望它应该给出基础知识.它目前仅支持文件数据和表单数据.

public class PostData
{

    private List m_Params;

    public List Params
    {
        get { return m_Params; }
        set { m_Params = value; }
    }

    public PostData()
    {
        m_Params = new List();

        // Add sample param
        m_Params.Add(new PostDataParam("email", "MyEmail", PostDataParamType.Field));
    }


    /// 
    /// Returns the parameters array formatted for multi-part/form data
    /// 
    /// 
    public string GetPostData()
    {
        // Get boundary, default is --AaB03x
        string boundary = ConfigurationManager.AppSettings["ContentBoundary"].ToString();

        StringBuilder sb = new StringBuilder();
        foreach (PostDataParam p in m_Params)
        {
            sb.AppendLine(boundary);

            if (p.Type == PostDataParamType.File)
            {
                sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName));
                sb.AppendLine("Content-Type: text/plain");
                sb.AppendLine();
                sb.AppendLine(p.Value);                 
            }
            else
            {
                sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name));
                sb.AppendLine();
                sb.AppendLine(p.Value);
            }
        }

        sb.AppendLine(boundary);

        return sb.ToString();           
    }
}

public enum PostDataParamType
{
    Field,
    File
}

public class PostDataParam
{


    public PostDataParam(string name, string value, PostDataParamType type)
    {
        Name = name;
        Value = value;
        Type = type;
    }

    public string Name;
    public string FileName;
    public string Value;
    public PostDataParamType Type;
}

要发送数据,您需要:

HttpWebRequest oRequest = null;
oRequest = (HttpWebRequest)HttpWebRequest.Create(oURL.URL);
oRequest.ContentType = "multipart/form-data";                       
oRequest.Method = "POST";
PostData pData = new PostData();

byte[] buffer = encoding.GetBytes(pData.GetPostData());

// Set content length of our data
oRequest.ContentLength = buffer.Length;

// Dump our buffered postdata to the stream, booyah
oStream = oRequest.GetRequestStream();
oStream.Write(buffer, 0, buffer.Length);
oStream.Close();

// get the response
oResponse = (HttpWebResponse)oRequest.GetResponse();

希望这很清楚,我已经从几个来源切割和粘贴,以使得更整洁.



3> codevision..:

使用.NET 4.5,您当前可以使用System.Net.Http命名空间.下面是使用多部分表单数据上传单个文件的示例.

using System;
using System.IO;
using System.Net.Http;

namespace HttpClientTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var client = new HttpClient();
            var content = new MultipartFormDataContent();
            content.Add(new StreamContent(File.Open("../../Image1.png", FileMode.Open)), "Image", "Image.png");
            content.Add(new StringContent("Place string content here"), "Content-Id in the HTTP"); 
            var result = client.PostAsync("https://hostname/api/Account/UploadAvatar", content);
            Console.WriteLine(result.Result.ToString());
        }
    }
}



4> jumoel..:

以dnolans为例,这是我实际可以使用的版本(边界有一些错误,编码没有设置):-)

要发送数据:

HttpWebRequest oRequest = null;
oRequest = (HttpWebRequest)HttpWebRequest.Create("http://you.url.here");
oRequest.ContentType = "multipart/form-data; boundary=" + PostData.boundary;
oRequest.Method = "POST";
PostData pData = new PostData();
Encoding encoding = Encoding.UTF8;
Stream oStream = null;

/* ... set the parameters, read files, etc. IE:
   pData.Params.Add(new PostDataParam("email", "example@example.com", PostDataParamType.Field));
   pData.Params.Add(new PostDataParam("fileupload", "filename.txt", "filecontents" PostDataParamType.File));
*/

byte[] buffer = encoding.GetBytes(pData.GetPostData());

oRequest.ContentLength = buffer.Length;

oStream = oRequest.GetRequestStream();
oStream.Write(buffer, 0, buffer.Length);
oStream.Close();

HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse();

PostData类应如下所示:

public class PostData
{
    // Change this if you need to, not necessary
    public static string boundary = "AaB03x";

    private List m_Params;

    public List Params
    {
        get { return m_Params; }
        set { m_Params = value; }
    }

    public PostData()
    {
        m_Params = new List();
    }

    /// 
    /// Returns the parameters array formatted for multi-part/form data
    /// 
    /// 
    public string GetPostData()
    {
        StringBuilder sb = new StringBuilder();
        foreach (PostDataParam p in m_Params)
        {
            sb.AppendLine("--" + boundary);

            if (p.Type == PostDataParamType.File)
            {
                sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName));
                sb.AppendLine("Content-Type: application/octet-stream");
                sb.AppendLine();
                sb.AppendLine(p.Value);
            }
            else
            {
                sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name));
                sb.AppendLine();
                sb.AppendLine(p.Value);
            }
        }

        sb.AppendLine("--" + boundary + "--");

        return sb.ToString();
    }
}

public enum PostDataParamType
{
    Field,
    File
}

public class PostDataParam
{
    public PostDataParam(string name, string value, PostDataParamType type)
    {
        Name = name;
        Value = value;
        Type = type;
    }

    public PostDataParam(string name, string filename, string value, PostDataParamType type)
    {
        Name = name;
        Value = value;
        FileName = filename;
        Type = type;
    }

    public string Name;
    public string FileName;
    public string Value;
    public PostDataParamType Type;
}



5> eeeeaaii..:

在我使用的.NET版本中你也必须这样做:

System.Net.ServicePointManager.Expect100Continue = false;

如果不这样做,HttpWebRequest该类将自动添加Expect:100-continue请求标题,这会对所有内容造成污染.

另外,我学到了很难获得正确数量的破折号.无论你说什么,Content-Type标题中的"边界" 必须以两个破折号开头

--THEBOUNDARY

最后

--THEBOUNDARY--

与示例代码中的完全一样.如果您的边界是大量的破折号后跟一个数字,那么通过查看代理服务器中的http请求,这个错误将不会很明显

推荐阅读
虎仔球妈_459
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有