Search code examples
asp.net-web-apihttpwebrequestmultipartform-datawebrequest

Multipart request rejected by Web API


I'm trying to send a Multipart request to a Web API using HttpWebRequest. The request I'm sending has the following format:

----------636194206488346738
Content-Disposition: form-data; name="file"; filename="A.png"
Content-Type:application/octet-stream
Content-Transfer-Encoding: binary
{Binary data in here}
----------636194206488346738--
{new line at the end}

And the request configuration is as follows:

Content-Type:"multipart/form-data; boundary=----------636194206488346738
Method: POST
Keep-Alive: True

When sending the request to the web API I get the Invalid end of stream error. However, I tried to convert the stream to text to see the actual data and it matches the example I added above.

However, when I'm using the WebClient and call the UploadFile method for the same purpose, I can successfully upload files to the API without any problem suggesting that something is wrong with my approach which is as follows.

My Constants:

Boundary = DateTime.Now.Ticks.ToString();
ContentType = "multipart/form-data; boundary=" + BoundaryDelimiter + Boundary;
BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "\r\n");
EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "--\r\n");

Method for formatting form data:

private Byte[] FormDataFormat(String name, String fileName, String contentType)
        => System.Text.Encoding.UTF8.GetBytes(String.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type:{2}\r\nContent-Transfer-Encoding: binary\r\n\r\n", name, fileName, contentType));

Attaching file to a stream:

Stream = new MemoryStream();
foreach (var i in files) {
    var tempEncode = FormDataFormat("file", i, "application/octet-stream");
    var file = System.IO.File.ReadAllBytes(i); // Files are supposed to be small.
    Stream.Write(BeginContent, 0, BeginContent.Length);
    Stream.Write(tempEncode, 0, tempEncode.Length);
    Stream.Write(file, 0, file.Length);
    ContentLenght += BeginContent.Length + tempEncode.Length + file.Length;
}
Stream.Write(EndContent, 0, EndContent.Length);
ContentLenght += EndContent.Length;

Creating the request:

public HttpWebRequest Request(String method) {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
        request.ContentType = ContentType;
        request.KeepAlive = true;
        request.Method = method;
        request.ContentLength = ContentLenght;
        Stream.Seek(0, SeekOrigin.Begin);
        Stream.CopyTo(request.GetRequestStream());
        Stream.Dispose();
        return request;
    }

Solution

  • You haven't shown in your question the BoundaryDelimiter variable declaration but for the purpose of this answer I will assume that it is defined like this:

    BoundaryDelimiter = "---------------------"
    

    Now the problem with your code is that you are missing a couple of dashes (--) when calculating your boundary delimiters in order to conform to RFC2388.

    So instead of:

    BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "\r\n");
    EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "--\r\n");
    

    You need this:

    BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + "--" + Boundary + "\r\n");
    EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + "--" + Boundary + "--\r\n");
    

    Notice the additional -- that I have added to your begin and end content boundary delimiters.

    Check the example provided here:

    Content-Type: multipart/form-data; boundary=AaB03x
    
    --AaB03x
    Content-Disposition: form-data; name="submit-name"
    
    Larry
    --AaB03x
    Content-Disposition: form-data; name="files"; filename="file1.txt"
    Content-Type: text/plain
    
    ... contents of file1.txt ...
    --AaB03x--
    

    Notice how the boundary is equal to AaB03x but the delimiter is actually --AaB03x and of course the end delimiter is --AaB03x--.

    As a side note you could get rid of the ContentLenght variable (which you have misspelled by the way :-)) and remove this line:

    request.ContentLength = ContentLenght
    

    This will simplify your code as the HttpWebRequest class will automatically populate the Content-Length header depending on the number of bytes you have written to the request stream.

    This being said, I would recommend you using the HttpClient class instead of HttpWebRequest because it already has built-in capabilities for properly encoding this kind of stuff. It will definitely make your code more readable as you will not have to worry about boundaries, delimiters and content lengths.