Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get a 404 trying to post a large file to a Core Web API

I am very new to file transfer between HttpClient and a Web API, so please excuse any ignorance and guesswork in my code. I have been trying to post a file created with System.IO.Compression.ZipFile to my web API now for about a day, and always I get a 404 response. If I post with an empty stream, the API action method is invoked, so I know the 404 is due to content and not the URI.

This method is in the client WPF application that attempts to post the file:

public async Task PostDirAsync(string localDirPath, string serverDir)
{
    var sourcePath = Path.Combine("Temp", Guid.NewGuid() + ".zip");
    ZipFile.CreateFromDirectory(localDirPath, sourcePath, CompressionLevel.Fastest, true);
    StreamContent streamContent;
    using (var fs = File.Open(sourcePath, FileMode.Open))
    {
        var outStream = new MemoryStream();
        await fs.CopyToAsync(outStream);
        outStream.Position = 0;
        streamContent = new StreamContent(outStream);
    }
    streamContent.Headers.Add("Content-Type", "application/octet-stream");
    var resp = await _client.PostAsync("api/File/PostDir?serverPath={WebUtility.UrlEncode(serverDir)}", streamContent);
}

And this is the action method in the Web API that receives the post, but only if I don't do the outStream.Position = 0; before attempting to post:

[HttpPost("PostDir")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PostDir(string serverPath)
{           
    var zipName = Path.Combine(_config["QuickDrive:TempDir"], Guid.NewGuid() + ".zip");
    using (var ms = new MemoryStream())
    using (var fileStream = System.IO.File.Create(zipName))
    {
        await Request.Body.CopyToAsync(ms);
        ms.Position = 0;
        await ms.CopyToAsync(fileStream);
    }
    return Ok();
}

The action method is invoked and runs without error with an empty stream, but is pretty useless as it writes an empty file. What am I doing wrong?

like image 494
ProfK Avatar asked Jun 07 '18 09:06

ProfK


People also ask

Why do I get a 404 error in ASP NET?

Why do I get a 404? Show activity on this post. The default action selection behavior in ASP.NET Web API cares about your action method parameters as well. If they are simple type objects and they are not optional, you will need to supply them in order to invoke that particular action method.

What does 404 not found mean in an API response?

The information returned with the response is dependent on the method used in the request. 404 Not Found - The server has not found anything matching the Request-URI. In the context of your API, it very much depends on how queries are created and how objects are retrieved. But, my interpretation has always been that:

Why am I getting 404's on all pages?

If you're getting 404 on all pages, I would suggest to make sure you don't have any other problems on your website - that the file structure looks similar to a default WordPress and double-check if you're not missing any files/folders (you can download a default WordPress core here ).

When is a 404 response appropriate for an application?

Now, if you consider the application to be just an extension of the server, and the subitem (tree) to be the actual resource, then a 404 response is appropriate: the server has merely delegated the task of finding the actual resource to the application, which it turn has failed to do so.


1 Answers

As mentioned in the comments, your first problem was that the Stream instances involved in the file copying were not being reset using Stream.Position = 0. I know you've made these changes already, but I just want to emphasise that this is a two-part solution.

So, the second part:

In your example code, you've added the [DisableRequestSizeLimit] annotation in order to bypass the default ASP.NET Core 2.0+ Kestrel request limits. However, there's also a limit that's imposed by IIS, which is 30MB by default. When this size limit is exceeded, IIS itself generates a 404 response, which is what you're seeing.

This answer explains how to change this limit using a custom Web.config (included below for completeness):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <!-- 1 GB -->
        <requestLimits maxAllowedContentLength="1073741824" />
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

As somewhat of a side note:

Unless you have a specific reason to do so, you can avoid the use of MemoryStream in your code and just pass fs directly into new StreamContent(...). You can do something similar with the Request.Body stream and copy that directly into the output FileStream. This would end up with:

public async Task PostDirAsync(string localDirPath, string serverDir)
{
    var sourcePath = Path.Combine("Temp", Guid.NewGuid() + ".zip");
    ZipFile.CreateFromDirectory(localDirPath, sourcePath, CompressionLevel.Fastest, true);

    var streamContent = new StreamContent(File.Open(sourcePath, FileMode.Open));
    streamContent.Headers.Add("Content-Type", "application/octet-stream");
    var resp = await _client.PostAsync("api/File/PostDir?serverPath={WebUtility.UrlEncode(serverDir)}", streamContent);
}

And with:

[HttpPost("PostDir")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PostDir(string serverPath)
{           
    var zipName = Path.Combine(_config["QuickDrive:TempDir"], Guid.NewGuid() + ".zip");
    using (var fileStream = System.IO.File.Create(zipName))
        await Request.Body.CopyToAsync(fileStream );
    return Ok();
}
like image 160
Kirk Larkin Avatar answered Oct 22 '22 00:10

Kirk Larkin