Multipart Form Post in C#

来源:互联网 发布:sybase数据库 编辑:程序博客网 时间:2024/06/02 04:01

I recently had to access a web API through C Sharp that required a file upload. This is pretty easy if you have an HTML page with a form tag and you want a user to directly upload the file.

<form method="POST" action="http://localhost/" enctype="multipart/form-data">File : <input type="file" name="content" size="38" /><br /><input type="hidden" name="id" value='fileUpload' /> </form>

However, this is not always a reasonable path to take. Sometimes you may be wanting to access a file that is already in a system and you don’t want a new upload. If you are accessing an external API, this is probably always the case. Unfortunately, building this post using C# is not quite as straightforward. I first tried using the WebClient UploadFile method, but it didn’t fit my needs because I wanted to upload form values (id, filename, other API specific parameters) in addition to just a file.

So, I needed to roll my own form post. Here is the Multipart Form RFC and the W3C Specification for multipart/form data. After reading these links and searching some forums, here is what I came up with.

Update: This post has gotten a great response from all the readers who have taken the time to comment and contribute. I would like to take this opportunity to promotethe best REST Client for .NET, RestSharp. John Sheehan has implemented this technique using code from this post, which can be seen on github (just look for WriteMultipartFormData). He has also done a great job implementing other basic REST operations in a fully tested suite. I would recommend reading the rest of the post to figure out what is going on behind the scenes, but you might consider using RestSharp in a production environment. Thanks for reading!

Note: If anyone is interested in this code in Visual Basic, reader Mike Ferreira converted the code into VB.Net in a comment below.

// Implements multipart/form-data POST in C# http://www.ietf.org/rfc/rfc2388.txt// http://www.briangrinstead.com/blog/multipart-form-post-in-cpublic 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;        }    }}

Here is the code to call the MultipartFormDataPost function with multiple parameters, including a file.

 // Read file dataFileStream 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 objectsDictionary<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 responsestring postURL = "http://localhost";string userAgent = "Someone";HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters); // Process responseStreamReader responseReader = new StreamReader(webResponse.GetResponseStream());string fullResponse = responseReader.ReadToEnd();webResponse.Close();Response.Write(fullResponse);

0 0
原创粉丝点击