Search code examples
c#asp.net-mvcasp.net-web-apidotnet-httpclient

Send PDF file in HTTP POST request using ASP.NET Web API


I created a web API using the ASP.NET Core Web API project template in Visual Studio to allow clients to upload files to my application. I created the controller which handles request from clients that want to upload PDF reports to the server. Here is the code for the action method:

[HttpPost("Report/{reportId")]
public async Task<IActionResult> AttachReport(string reportId)
{
    Reporter client = new Reporter();
    await client.AttachReportToRequest(reportId);
    return 
}

This is the method that actually sends the request to my application server.

public async Task<HttpResponseMessage> AttachReportToRequest(string reportId)
{
    try
    {
        if (authToken is null)
        {
            authToken = await login("username", "passw0rd");
        }
        HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/to/app/server/reportInfo");
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("app-JWT", authToken);

        string scanReportFile = $"{Directory.GetCurrentDirectory()}\\Reports\\sample_report.pdf";
        MultipartFormDataContent requestBody = new MultipartFormDataContent();

        //Create values JSON payload
        requestBody.Add(new StringContent($"\"values\":{{
            \"ReportId\":\"{reportId}\",
            \"Description\":\"test\",
            \"Submitter\":\"John Smith\",
            \"Number of Attachment\":\"1\"
            }}"), "entry");
        
        requestBody.Add(new StreamContent(File.OpenRead("C:/Users/myuser/Documents/ReporterApp/myreport.pdf")), "file-attachment", "C:/Users/myuser/Documents/ReporterApp/myreport.pdf");

        requestMessage.Content = requestBody;
        HttpResponseMessage attachmentResponse = await httpClient.SendAsync(requestMessage);
        return attachmentResponse;
            }
    catch (Exception)
    {

        throw;
    }
}

The issue I'm having is that every time the httpClient tries to send the request the client times out. I never receive any kind of HTTP response from the server. I tried hitting the same application using the same endpoint and the same multipart/form-data content type from Postman and it works. Is there any particular reason why the SendAsync method would be timing out when sending the request?

I tried putting the FileStream in a using block and tried sending different file types but none of those things worked.


Solution

  • After many, many long hours, I was finally able to figure it out. It turns out there are actually a few things wrong with my code. I initially thought there was an issue with the boundary parameter of the Content-Type header. As it turns out, the .NET Framework automatically takes care of generating the boundary for you.

    The first thing I missed was that, even though MultipartFormDataContent is a subclass of HttpContent, it is actually a container of HttpContent objects. A key difference between attaching a single HttpContent object to your request and attaching an object of type MultipartFormDataContent is that for every object you add to the container you need to specify a Content-Type header; without that header for each object the server won't know what the type of each part is. For example, in the first call to requestBody.Add() I am passing it a StringContent object with the JSON payload string. However, the StringContent constructor has an overload where you can specify the encoding and media type. Without the "application/json", the StringContent object defaults to a media type of "text/plain", which won't work if the server is expecting JSON. Doing this solves the first problem:

    requestBody.Add(new StringContent($"...", Encoding.UTF8, "application/json"), "entry")
    

    The second issue was pretty much the same as the first. Rather than create the StreamContent object inline with the call to requestBody.Add(), I could first assign the StreamContent object to a variable and then add the necessary headers to it. Since the type of file I'm sending is a pdf, the correct media type is "application/octet-stream". This is what worked for me.

    StreamContent myContent = new StreamContent(File.OpenRead("C:/User/.../report.pdf"));
    myContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
    requestBody.Add(myContent, "attachment");
    

    Once I did this, the HttpClient sent the request successfully and no longer timed out. You could also do something like this with the File.ReadAllBytes() method:

    ByteArrayContent fileContents = new ByteArrayContent(File.ReadAllBytes("path/to/file"));
    fileContents.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
    

    This should also work.