Sunday, March 21, 2010

Multipart Form Upload Helper for System.Net.WebClient


Implementing Multipart-MIME upload helper using WebClient


The WebClient class is a higher level abstraction on top of HttpWebRequest, that abstracts away the common tasks of uploading files, as well as doing Form Posts to HTTP servers. However, it does not provide an easy way to do multipart form uploads.

Multipart form upload is defined by the W3C specification.

Multipart HTML Form Design


Let us start off by designing the HTML Form that will be the target of the multipart upload. As per the HTML spec, the form should have an enctype attribute set to "multipart/form-data". Also, we are going to have a File input control embedded in the form.

MIME Part Requirements

In a multipart form upload, each mime part begins with some MIME headers. The following headers are mandatory:

  • Content-Disposition header
  • A name attribute that specifies the name of the corresponding control from the HTML form.
For eg, in the form above, if I specify "John" for the name, and "1" for the ID, then I will have the following in the MIME upload

--BD37EC1
Content-Disposition: form-data; name="fname";

john
--BD37EC1
Content-Disposition: form-data; name="id";
acme
--BD37EC1
Ideally, our helper class should be able to write out the boundaries itself, and delegate the task of writing out the headers to each MIME part that is part of the upload.

Our multipart form has two kinds of controls: normal Text input as well as FILE input controls. Therefore, our helper class needs to support both of these types of MIME parts.

Let us start off by thinking about the layour of the MIME part classes.

MIME Part Design


As discussed, we are going to have two types of MIME parts - a normal FORM upload type where we want to specify Name/Value pairs, as well as a FILE part where we want to embed the contents of a file.

In the previous section we saw the basic requirements of a MIME part. It has to have a "Content-Disposition" header. In addition to this, a MIME part that contains file contents should also have a "Content-Type" header.

With this, we can now write out the layout of the base class for these two MIME parts.



This is a good abstraction of a MIME part. Derived classes can define the Content-Type and Content-Disposition, we well as write out the contents of the MIME part into the Stream that is provided. The MIME helper class will do the job of formatting the MIME part and uploading it to a HTTP endpoint.

With this, we can now write out a derived class that can upload textual input data (i.e not file contents)


NameValuePart is an implementation of the MimePart abstract class. It does the job of serializing the Name/Value pairs as a MIME encapsulated part.

Next, we write the implementation of the class the encapsulates file uploads as a MIME part.



According to the HTML Form upload specification, when the form defines more than one FILE INPUT field, then all the files in the form can be uploaded as one MIME type (of type multipart/mixed) that encapsulates each file, and each file will have it's own MIME encapsulation. In order to support this, we need to create a special class that can encapsulate FILE types.



This class does the job of encapsulating FILE parts inside a MIME container.

Now, we just need to write the code that can put this all together.


Since the job of serializing to MIME has been delegated to each concrete implementation of MimePart class, the MultipartHelper only has to make sure that it uses the different parts and serializes them into a stream. The stream is then uploaded using the supplied WebClient.

Note that I have put the FilesCollection class as an inner class of the MultipartHelper class, because it is meant to be a helper class that should not be directly accessed by the clients.

The client program for this looks as follows:


If you want to understand what is going on at the wire level, you can create a system.net trace log which will show the details.

Complete Library


You can find the complete library at multipart-upload-helper repository in my Github.

Conclusion


We implemented a helper library that uses System.Net.WebClient to upload multipart form data.