Je suis très nouveau dans le transfert de fichiers entre HttpClient
et une API Web, veuillez donc excuser toute ignorance et conjecture dans mon code. J'ai essayé de publier un fichier créé avec System.IO.Compression.ZipFile
à mon API Web depuis environ une journée, et je reçois toujours une réponse 404. Si je poste avec un flux vide, la méthode d'action API est invoquée, donc je sais que le 404 est dû au contenu et non à l'URI.
Cette méthode se trouve dans l'application cliente WPF qui tente de publier le fichier:
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);
}
Et c'est la méthode d'action dans l'API Web qui reçoit le message, mais seulement si je ne fais pas le outStream.Position = 0;
avant de tenter de publier:
[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();
}
La méthode d'action est invoquée et s'exécute sans erreur avec un flux vide, mais est assez inutile car elle écrit un fichier vide. Qu'est-ce que je fais mal?
Comme mentionné dans les commentaires, votre premier problème était que les instances Stream
impliquées dans la copie de fichiers n'étaient pas réinitialisées à l'aide de Stream.Position = 0
. Je sais que vous avez déjà apporté ces modifications, mais je tiens simplement à souligner qu'il s'agit d'une solution en deux parties.
Donc, la deuxième partie:
Dans votre exemple de code, vous avez ajouté l'annotation [DisableRequestSizeLimit]
Afin de contourner les limites de demande par défaut ASP.NET Core 2.0+ Kestrel. Cependant, il existe également une limite imposée par IIS, qui est de 30 Mo par défaut. Lorsque cette taille limite est dépassée, IIS lui-même génère une réponse 404, ce que vous voyez.
Cette réponse explique comment modifier cette limite en utilisant un Web.config
Personnalisé (inclus ci-dessous pour être complet):
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<!-- 1 GB -->
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
</system.webServer>
</configuration>
Comme un peu d'une note latérale:
Sauf si vous avez une raison spécifique de le faire, vous pouvez éviter l'utilisation de MemoryStream
dans votre code et simplement passer fs
directement dans new StreamContent(...)
. Vous pouvez faire quelque chose de similaire avec le flux Request.Body
Et le copier directement dans la sortie FileStream
. Cela aboutirait à:
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);
}
Et avec:
[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();
}