web-dev-qa-db-fra.com

Comment retourner un fichier (FileContentResult) dans ASP.NET WebAPI

Dans un contrôleur MVC standard, nous pouvons générer un fichier pdf avec un FileContentResult.

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

Mais comment pouvons-nous le changer en un ApiController?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

Voici ce que j'ai essayé mais cela ne semble pas fonctionner.

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

Le résultat renvoyé affiché dans le navigateur est:

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

Et il y a un article similaire sur SO: Retour du fichier binaire du contrôleur dans l’API Web ASP.NET . Il parle de la sortie d'un fichier existant. Mais je ne pouvais pas le faire fonctionner avec un flux.

Aucune suggestion?

154
Blaise

Au lieu de retourner StreamContent sous la forme Content, je peux le faire fonctionner avec ByteArrayContent.

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}
176
Blaise

Si vous voulez retourner IHttpActionResult vous pouvez le faire comme ceci:

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}
81
Ogglas

Cette question m'a aidé.

Alors, essayez ceci:

Code du contrôleur:

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

Voir le balisage HTML (avec événement click et URL simple):

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>
40
aleha

Je ne sais pas exactement quelle partie blâmer, mais voici pourquoi MemoryStream ne fonctionne pas pour vous:

Lorsque vous écrivez dans MemoryStream, il incrémente sa propriété Position. Le constructeur de StreamContent prend en compte le courant du flux Position. Donc, si vous écrivez dans le flux, puis le passez à StreamContent, la réponse commencera à partir du néant à la fin du flux.

Il y a deux façons de résoudre ce problème:

1) construire le contenu, écrire dans le flux

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    // ...
    // stream.Write(...);
    // ...
    return response;
}

2) écrire pour diffuser, réinitialiser la position, construire le contenu

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    // ...
    // stream.Write(...);
    // ...
    stream.Position = 0;

    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    return response;
}

2) est un peu mieux si vous avez un nouveau flux, 1) est plus simple si votre flux ne commence pas à 0

6
M.Stramm

Voici une implémentation qui diffuse le contenu du fichier sans le mettre en mémoire tampon (la mise en tampon d'octets []/MemoryStream, etc. peut être un problème de serveur si le fichier est volumineux).

public class FileResult : IHttpActionResult
{
    public FileResult(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        FilePath = filePath;
    }

    public string FilePath { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(File.OpenRead(FilePath));
        var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
        return Task.FromResult(response);
    }
}

Il peut être simplement utilisé comme ceci:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}
4
Simon Mourier

Pour moi c'était la différence entre

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

et

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

Le premier renvoyait la représentation JSON de StringContent: {"Headers": [{"Key": "Content-Type", "Value": ["application/octet-stream; charset = utf-8"]}}}

Pendant que le second renvoyait le fichier proprement dit.

Il semble que Request.CreateResponse ait une surcharge qui prend une chaîne en tant que second paramètre, ce qui semble avoir été à l'origine du rendu de l'objet StringContent lui-même en tant que chaîne, au lieu du contenu réel.

3
EnderWiggin