1

I have a .Net Core 2.0 application that is sending files to a Web API endpoint, using multipart content. Everywhere I've looked, such as C# HttpClient 4.5 multipart/form-data upload, makes it seem that it should be as easy as passing a FileStream to a StreamContent. However, when I make the post, it looks like the file is attaching as text, not bits.

Actual code:

var request = new HttpRequestMessage()
{
    Method = HttpMethod.Post,
    RequestUri = new Uri( "http://localhost:10442/filetest" )
};
var multiContent = new MultipartFormDataContent();

var filestream = File.OpenRead( path );
var filename = Path.GetFileName( path );
var streamContent = new StreamContent( filestream );
streamContent.Headers.Add( "Content-Type", "application/octet-stream" );
streamContent.Headers.Add( "Content-Disposition", $"form-data; name=\"file1\"; filename=\"{filename}\"" );

multiContent.Add( streamContent, "file", filename );
request.Content = multiContent;

var response = await new HttpClient().SendAsync( request );

The request looks like this which, as you may notice, is not all on one line (which I think is a/THE problem):

POST http://localhost:10442/filetest HTTP/1.1
Content-Type: multipart/form-data; boundary="c5295887-425d-4ec7-8638-20c6254f9e4b"
Content-Length: 88699
Host: localhost:10442

--c5295887-425d-4ec7-8638-20c6254f9e4b
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file1"; filename="somepdf.pdf"

%PDF-1.7
%       
1 0 obj
<</Type/Catalog/Version/1.7/Pages 3 0 R/Outlines 2 0 R/Names 8 0 R/Metadata 31 0 R>>
endobj

Fiddler shows the entire post all the way down to the end boundary, but await Request.Content.ReadAsStringAsync() in the endpoint only shows the first couple dozen bytes (it looks as if the stream wasn't finished, but if Fiddler got it all, shouldn't my endpoint have too?).

I was having similar trouble trying to hit a remote endpoint; I built this endpoint to test locally.

The exception I'm getting is:"Unexpected end of MIME multipart stream. MIME multipart message is not complete." To me, this makes sense both if I'm really only getting part of my stream, or if the line breaks are throwing something off.

I have also tried throwing some of the Idisposables into Usings but, as expected, that closes the streams and I get exceptions that way.

And for completeness's sake, here's the endpoint I'm calling:

public async void ReceiveFiles()
{
    // exception happens here:
    var mpData = await Request.Content.ReadAsMultipartAsync();
    await Task.FromResult( 0 );
}
emery.noel
  • 1,073
  • 1
  • 9
  • 24
  • Why do you want to use a Form body instead of Chunked Transfer coding, or just setting a Content-Length header and putting the file bytes in one request body? – David Browne - Microsoft Oct 20 '18 at 18:37
  • Example send parameters as multipeart dotnet 3.0 in answer https://stackoverflow.com/a/59308653/5333683 – D.Oleg Dec 12 '19 at 16:31

3 Answers3

3

Try something like this:

static int Main(string[] args)
{
    var request = new HttpRequestMessage()
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("http://localhost:10442/filetest")
    };
    var path = "c:\\temp\\foo.bak";

    using (var filestream = File.OpenRead(path))
    {
        var length = filestream.Length.ToString();
        var streamContent = new StreamContent(filestream);
        streamContent.Headers.Add("Content-Type", "application/octet-stream");
        streamContent.Headers.Add("Content-Length", length);


        request.Content = streamContent;

        Console.WriteLine($"Sending {length} bytes");
        var response = new HttpClient().SendAsync(request).Result;


        Console.WriteLine(response.Content.ReadAsStringAsync().Result);
    }

    Console.WriteLine("Hit any key to exit");
    Console.ReadKey();
    return 0;
}

and

    [HttpPost]
    public async Task<IActionResult> Upload()
    {
        var buf = new byte[1024 * 64];
        long totalBytes = 0;
        using (var rs = Request.Body)
        {
            while (1 == 1)
            {
                int bytesRead = await rs.ReadAsync(buf, 0, buf.Length);
                if (bytesRead == 0) break;
                totalBytes += bytesRead;
            }
        }
        var uploadedData = new
        {
            BytesRead = totalBytes
        };
        return new JsonResult(uploadedData) ;
    }
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
0

I'm trying to solve a similar issue, and I'm not 100% to a solution yet, but maybe some of my research can help you.

It was helpful to me to read through the microsoft docs for .NET core file uploads, specifically for large files that use streams and multipart form data: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1#uploading-large-files-with-streaming

You already referenced it, but there's some relevant useful information in this answer: C# HttpClient 4.5 multipart/form-data upload

This explains the details of the content-disposition header and how it is used with multipart form data requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#As_a_header_for_a_multipart_body

As to your specific problem of the file being sent as text instead of bits, since http is text-based, it can only be sent as text, but that text can be encoded as you see fit. Perhaps your StreamContent needs a specific encoding to be used, like base64 encoding or similar? I do believe the newlines are significant in the multipart request, so hopefully setting the encoding for the file content as needed would be enough.

Another possibility: could it be that you need to set additional information on the file section's headers or in the definition of the StreamContent to indicate that it should expect to continue, or that the boundary information isn't put in correctly? See Multipart forms from C# client

Tommy Elliott
  • 341
  • 1
  • 3
  • 9
0

I use this lib : https://github.com/jgiacomini/Tiny.RestClient It's make easier to send multiplart file to send multipart files.

Here a sample : await client.PostRequest("MultiPart/Test"). AsMultiPartFromDataRequest(). AddStream(stream1, "request", "request2.bin"). AddStream(stream2, "request", "request2.bin") ExecuteAsync();

user8803505
  • 126
  • 1
  • 5