web-dev-qa-db-fra.com

Renvoi d'un fichier binaire à partir du contrôleur dans l'API Web ASP.NET

Je travaille sur un service Web utilisant la nouvelle interface WebAPI d'ASP.NET MVC qui servira des fichiers binaires, principalement des fichiers .cab et .exe.

La méthode de contrôleur suivante semble fonctionner, ce qui signifie qu'elle renvoie un fichier, mais elle définit le type de contenu sur application/json:

public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
    var path = @"C:\Temp\test.exe";
    var stream = new FileStream(path, FileMode.Open);
    return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}

Y a-t-il une meilleure manière de faire cela?

304
Josh Earl

Essayez d’utiliser un simple HttpResponseMessage dont la propriété Content est définie sur a StreamContent :

_// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;

public HttpResponseMessage Post(string version, string environment,
    string filetype)
{
    var path = @"C:\Temp\test.exe";
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = 
        new MediaTypeHeaderValue("application/octet-stream");
    return result;
}
_

Quelques points à noter sur le stream utilisé:

  • Vous ne devez pas appeler stream.Dispose(), car l'API Web doit toujours pouvoir y accéder lorsqu'elle traite la méthode result de la méthode du contrôleur pour renvoyer des données au client. Par conséquent, n'utilisez pas de bloc using (var stream = …). L'API Web disposera du flux pour vous.

  • Assurez-vous que la position actuelle du flux est définie sur 0 (c’est-à-dire le début des données du flux). Dans l'exemple ci-dessus, il s'agit d'une donnée puisque vous venez tout juste d'ouvrir le fichier. Cependant, dans d’autres scénarios (comme lorsque vous écrivez pour la première fois des données binaires sur un MemoryStream), assurez-vous de stream.Seek(0, SeekOrigin.Begin); ou définissez _stream.Position = 0;_

  • Avec les flux de fichiers, spécifier explicitement l'autorisation FileAccess.Read peut aider à prévenir les problèmes de droits d'accès sur les serveurs Web; IIS les comptes de pool d'applications ne disposent souvent que de droits d'accès en lecture/liste/exécution au wwwroot.

494
carlosfigueira

Pour Web API 2, vous pouvez implémenter IHttpActionResult. Voici la mienne:

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

class FileResult : IHttpActionResult
{
    private readonly string _filePath;
    private readonly string _contentType;

    public FileResult(string filePath, string contentType = null)
    {
        if (filePath == null) throw new ArgumentNullException("filePath");

        _filePath = filePath;
        _contentType = contentType;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StreamContent(File.OpenRead(_filePath))
        };

        var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);

        return Task.FromResult(response);
    }
}

Ensuite, quelque chose comme ceci dans votre contrôleur:

[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
    var serverPath = Path.Combine(_rootPath, imagePath);
    var fileInfo = new FileInfo(serverPath);

    return !fileInfo.Exists
        ? (IHttpActionResult) NotFound()
        : new FileResult(fileInfo.FullName);
}

Et voici une façon de dire à IIS d'ignorer les demandes portant une extension afin que celle-ci parvienne au contrôleur:

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
130
Ronnie Overby

Bien que la solution suggérée fonctionne bien, il existe un autre moyen de renvoyer un tableau d'octets à partir du contrôleur, avec le flux de réponses correctement formaté:

  • Dans la requête, définissez l'en-tête "Accept: application/octet-stream".
  • Côté serveur, ajoutez un formateur de type de support pour prendre en charge ce type mime.

Malheureusement, WebApi n’inclut aucun formateur pour "application/octet-stream". Il y a une implémentation ici sur GitHub: BinaryMediaTypeFormatter (il y a des adaptations mineures pour que cela fonctionne pour webapi 2, les signatures de méthodes ont été modifiées).

Vous pouvez ajouter ce formateur dans votre configuration globale:

HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));

WebApi doit maintenant utiliser BinaryMediaTypeFormatter si la demande spécifie l'en-tête Accept correct.

Je préfère cette solution car un contrôleur d’action renvoyant octet [] est plus facile à tester. Cependant, l’autre solution vous permet davantage de contrôle si vous souhaitez renvoyer un autre type de contenu que "application/octet-stream" (par exemple, "image/gif").

8
Eric Boumendil

Pour toute personne ayant le problème de l'appel de l'API appelé plus d'une fois lors du téléchargement d'un fichier assez volumineux à l'aide de la méthode de la réponse acceptée, définissez la mise en mémoire tampon de la réponse sur true. System.Web.HttpContext.Current.Response.Buffer = true;

Cela permet de s'assurer que tout le contenu binaire est mis en mémoire tampon côté serveur avant d'être envoyé au client. Sinon, plusieurs requêtes seront envoyées au contrôleur et si vous ne les gérez pas correctement, le fichier sera corrompu.

7
JBA

La surcharge que vous utilisez définit l'énumération des formateurs de sérialisation. Vous devez spécifier le type de contenu explicitement comme:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
6
David Peden

Pour ceux qui utilisent .NET Core:

Vous pouvez utiliser l'interface IActionResult dans une méthode de contrôleur API, comme suit ...

    [HttpGet("GetReportData/{year}")]
    public async Task<IActionResult> GetReportData(int year)
    {
        // Render Excel document in memory and return as Byte[]
        Byte[] file = await this._reportDao.RenderReportAsExcel(year);

        return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
    }

Cet exemple est simplifié, mais devrait faire passer le message. Dans .NET Core, ce processus est , donc bien plus simple que dans les versions précédentes de .NET - c’est-à-dire qu’il n’était pas possible de définir le type de réponse, le contenu, les en-têtes, etc.

En outre, bien sûr, le type MIME pour le fichier et l'extension dépendront des besoins individuels.

Référence: SO Post Answer par @NKosi

5
KMJungersen

Tu pourrais essayer

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
3
MickySmig