Reputation: 51064
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?
Upvotes: 14
Views: 6966
Reputation: 93063
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();
}
Upvotes: 22