Comment retourner un fichier à l'aide de l'API Web?

98

J'utilise l' API Web ASP.NET .
Je souhaite télécharger un PDF avec C # depuis l'API (que l'API génère).

Puis-je simplement demander à l'API de renvoyer un byte[]? et pour l'application C #, puis-je simplement faire:

byte[] pdf = client.DownloadData("urlToAPI");? 

et

File.WriteAllBytes()?
Kyle
la source
"l'API Web"? Que veux-tu dire exactement? Veuillez lire tinyurl.com/so-hints et modifier votre question.
Jon Skeet
1
@JonSkeet: l'API Web est une nouvelle fonctionnalité de la dernière version d'ASP.NET. Voir asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey
1
@Robert: Merci - la balise rend les choses plus claires, bien que se référer à "l'API Web ASP.NET" aurait été encore plus clair. En partie la faute de MS pour un nom générique nul aussi :)
Jon Skeet
Quiconque atterrit souhaitant renvoyer le flux via l'API Web et IHTTPActionResult, alors voir ici: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

Réponses:

170

Mieux vaut retourner HttpResponseMessage avec StreamContent à l'intérieur.

Voici un exemple:

public HttpResponseMessage GetFile(string id)
{
    if (String.IsNullOrEmpty(id))
        return Request.CreateResponse(HttpStatusCode.BadRequest);

    string fileName;
    string localFilePath;
    int fileSize;

    localFilePath = getFileFromID(id, out fileName, out fileSize);

    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentDisposition.FileName = fileName;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

    return response;
}

UPD du commentaire de patridge : Si quelqu'un d'autre arrive ici pour envoyer une réponse à partir d'un tableau d'octets au lieu d'un fichier réel, vous allez vouloir utiliser le nouveau ByteArrayContent (someData) au lieu de StreamContent (voir ici ).

Regfor
la source
1
Première chose - ce code provoquera une exception puisque vous créez deux objets FileStream pointés vers le même fichier. La deuxième chose est que vous ne souhaitez pas utiliser une instruction "Using", car dès que la variable sort du champ d'application, .NET la supprimera et vous obtiendrez des messages d'erreur concernant la fermeture de la connexion sous-jacente.
Brandon Montgomery
48
Si quelqu'un d'autre cherche à envoyer une réponse à partir d'un tableau d'octets au lieu d'un fichier réel, vous voudrez l'utiliser à la new ByteArrayContent(someData)place de StreamContent(voir ici ).
patridge
Vous pouvez également remplacer la base dispose () afin de pouvoir gérer correctement vos ressources lorsque l'infrastructure l'appelle.
Phil Cooper
1
Je tiens à souligner que le MediaTypeHeaderValue correct est crucial et pour le rendre dynamique si vous avez différents types de fichiers, vous pouvez le faire comme ceci. (où fileName est une chaîne et a un type de fichier se terminant par .jpg, .pdf, docx, etc.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue (contentType);
JimiSweden
1
Le FileStream est-il supprimé automatiquement?
Brian Tacker
37

J'ai fait l'action suivante:

[HttpGet]
[Route("api/DownloadPdfFile/{id}")]
public HttpResponseMessage DownloadPdfFile(long id)
{
    HttpResponseMessage result = null;
    try
    {
        SQL.File file = db.Files.Where(b => b.ID == id).SingleOrDefault();

        if (file == null)
        {
            result = Request.CreateResponse(HttpStatusCode.Gone);
        }
        else
        {
            // sendo file to client
            byte[] bytes = Convert.FromBase64String(file.pdfBase64);


            result = Request.CreateResponse(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = file.name + ".pdf";
        }

        return result;
    }
    catch (Exception ex)
    {
        return Request.CreateResponse(HttpStatusCode.Gone);
    }
}
André de Mattos Ferraz
la source
Cela répond en fait à la question
Mick
1
Ce ne serait pas une bonne idée avec des fichiers volumineux car il charge l'image entière en mémoire. L'option de flux est meilleure.
Paul Reedy
@PaulReedy Parfait ... mais dans de nombreux cas, vous n'avez pas besoin de traiter de gros fichiers. Mais je suis totalement d'accord avec votre point de vue!
André de Mattos Ferraz
14

Exemple avec IHttpActionResultin ApiController.

[HttpGet]
[Route("file/{id}/")]
public IHttpActionResult GetFileForCustomer(int id)
{
    if (id == 0)
      return BadRequest();

    var file = GetFile(id);

    IHttpActionResult response;
    HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);
    responseMsg.Content = new ByteArrayContent(file.SomeData);
    responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
    responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    response = ResponseMessage(responseMsg);
    return response;
}

Si vous ne souhaitez pas télécharger le PDF et utiliser un navigateur intégré à la visionneuse PDF, supprimez les deux lignes suivantes:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
Ogglas
la source
@ElbertJohnFelipe Oui, vous obtenez le fichier avec var file = GetFile(id);. file.SomeDataest un tableau d'octets ( byte[]) et l' file.FileNameest string.
Ogglas
Merci pour votre commentaire. 'HttpResponseMessage' ne fonctionnait pas pour moi dans un ApiController, vous m'avez donc sauvé.
Max
13

Juste une note pour .Net Core: Nous pouvons utiliser le FileContentResultet définir le contentType sur application/octet-streamsi nous voulons envoyer les octets bruts. Exemple:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}
Amir Shirazi
la source
1
Cela fonctionne très bien, aussi si vous voulez contrôler le nom du fichier, il y a une propriété FileContentResultappelée FileDownloadNamepour spécifier le nom du fichier
semainesdev
@weeksdev ah ne le savait pas. Merci pour le commentaire.
Amir Shirazi le
C'est tout, merci. Le commentaire de semainesdev est également très utile.
fragg
1

Je me suis demandé s'il y avait un moyen simple de télécharger un fichier d'une manière plus ... "générique". Je suis venu avec ça.

C'est un simple ActionResultqui vous permettra de télécharger un fichier à partir d'un appel de contrôleur qui renvoie un fichier IHttpActionResult. Le fichier est stocké dans le byte[] Content. Vous pouvez le transformer en flux si nécessaire.

Je l'ai utilisé pour renvoyer des fichiers stockés dans la colonne varbinary d'une base de données.

    public class FileHttpActionResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string FileName { get; set; }
        public string MediaType { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public byte[] Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = new HttpResponseMessage(StatusCode);

            response.StatusCode = StatusCode;
            response.Content = new StreamContent(new MemoryStream(Content));
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = FileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaType);

            return Task.FromResult(response);
        }
    }
Jake
la source
Une brève explication de la façon dont votre code résout le (s) problème (s) du PO améliorerait la qualité de votre réponse.
Adrian Mole