C# and .NET,  Geeks,  Programming

Posting content from memory stream using HttpWebRequest c#

So I needed to upload a zipped file that I was creating in memory and send it to an MVC action on my server. I read plenty of examples and perhaps I could have gone down the route of saving the stream to a file on disk and then using the .net WebClient UploadFile method. But I didn’t and so I’ve ended up with a class based off a number of help from various parties including an excellent example at .

A very good place to find the specification on what is needed is at . I found this helped alto and explained some of the missing pieces of information I needed to determine how to send this data.

Just a few notes on some things (limitations) I would like to change about this code if I ever need to adjust it’s functionality;

  • I currently only allows for a single memory stream and the content type of that stream is hard-coded. I would like to separate this out into a HttpFileUploadContent class that contains the Stream, Content-Type, Filename, formname perhaps to do this.
  • The class is responsible for sending and receiving the data to an HttpWebRequest. What if we needed to change the classed used? Separating this out, or perhaps supplying an interface via some form of injection would be better in this case.
  • The class also adds authentication headers and assumes that is required. In my case it was and I cannot see any need for it to change. But if there was a need I’d look at separating this out to something else has the responsibility for setting the authentication credentials and adding it as necessary.

Here’s how my first refactoring of the class went based on my initial requirements. If further refactoring goes based on my points above I will look at updating this post to show the changes.

One thing I did try and concentrate when creating this class was making sure my methods were small and did one thing only. Some of the examples I saw for this including everything into one big method and I found it quite hard to follow when trying to come to grips with what was required.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Specialized;
using System.Net;
using System.Globalization;
namespace CDAX.Schema.Zip
{
    public class HttpZipUpload
    {
        private ICredentials _credentials;
        private readonly string _boundary = "";

        public HttpZipUpload(ICredentials credentials)
        {
            _boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
            _credentials = credentials;
        }

        public byte[] Upload(string url, MemoryStream dataStream)
        {
            return Upload(url, dataStream, new NameValueCollection());
        }

        public byte[] Upload(string url, MemoryStream dataStream, NameValueCollection nameValues)
        {
            HttpWebRequest request = GetHttpWebRequest(url);
            WriteAuthenticationToRequest(request);

            dataStream.Position = 0;
            using (MemoryStream outputStream = new MemoryStream())
            {
                WriteNameValuesToStream(outputStream, nameValues);

                WriteDataContentToStream(outputStream, dataStream);

                WriteToHttpStream(request, outputStream);
            }

            // return the response
            return GetHttpWebResponse(request);
        }

        private byte[] GetHttpWebResponse(HttpWebRequest request)
        {
            using (var response = request.GetResponse())
            {
                using (var responseStream = response.GetResponseStream())
                {
                    using (var stream = new MemoryStream())
                    {
                        responseStream.CopyTo(stream);
                        return stream.ToArray();
                    }
                }
            }
        }

        private HttpWebRequest GetHttpWebRequest(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.ContentType = string.Format("multipart/form-data; boundary={0}", _boundary);
            request.Method = "POST";

            return request;
        }

        private void WriteAuthenticationToRequest(HttpWebRequest request)
        {
            var user = _credentials.GetCredential(request.RequestUri,"Basic");
            string auth = string.Format("{0}:{1}", user.UserName, user.Password);

            request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(auth)));            
        }

        private void WriteEndBoundaryToStream(MemoryStream stream)
        {
            WriteBoundaryToStream(stream, "--");
        }

        private void WriteBoundaryToStream(MemoryStream stream, string endDeliminator)
        {
            WriteToStream(stream, Encoding.ASCII, string.Format("--{0}{1}", _boundary, endDeliminator));
        }

        private void WriteDataContentToStream(MemoryStream outputStream, MemoryStream inputStream)
        {
            // write content boundary start
            WriteBoundaryToStream(outputStream, Environment.NewLine);

            string formName = "uploaded";
            string fileName = "input.zip";
            string contentType = "application/zip";

            WriteToStream(outputStream, System.Text.Encoding.UTF8, string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}", formName, fileName, Environment.NewLine));
            WriteToStream(outputStream, System.Text.Encoding.UTF8, string.Format("Content-Type: {0}{1}{1}", contentType, Environment.NewLine));

            byte[] buffer = new byte[inputStream.Length];
            int bytesRead = 0;

            while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
            {
                outputStream.Write(buffer, 0, bytesRead);
            }

            // must include a new line before writing the end boundary
            WriteToStream(outputStream, Encoding.ASCII, Environment.NewLine);

            // make sure we end boundary now as the content is finished
            WriteEndBoundaryToStream(outputStream);
        }

        private void WriteNameValuesToStream(MemoryStream stream, NameValueCollection nameValues)
        {
            foreach (string name in nameValues.Keys)
            {
                WriteBoundaryToStream(stream, Environment.NewLine);

                WriteToStream(stream, Encoding.UTF8, string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name, Environment.NewLine));
                WriteToStream(stream, Encoding.UTF8, nameValues[name] + Environment.NewLine);
            }
        }

        private void WriteToHttpStream(HttpWebRequest request, MemoryStream outputStream)
        {
            request.ContentLength = outputStream.Length;
            
            using (Stream requestStream = request.GetRequestStream())
            {
                outputStream.Position = 0;

                byte[] tempBuffer = new byte[outputStream.Length];
                outputStream.Read(tempBuffer, 0, tempBuffer.Length);
                outputStream.Close();

                requestStream.Write(tempBuffer, 0, tempBuffer.Length);
                requestStream.Close();
            }
        }

        private void WriteToStream(MemoryStream stream, Encoding encoding, string output)
        {
            byte[] headerbytes = encoding.GetBytes(output);
            stream.Write(headerbytes, 0, headerbytes.Length);
        }

    }
}

Hope this might help someone who ever needs to do this.

Leave a Reply