So geben Sie eine Datei (FileContentResult) in ASP.NET WebAPI zurück

173

In einem normalen MVC-Controller können wir PDF mit a ausgeben FileContentResult.

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

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

Aber wie können wir es in ein ändern ApiController?

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

Folgendes habe ich versucht, aber es scheint nicht zu funktionieren.

[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);            
}

Das zurückgegebene Ergebnis, das im Browser angezeigt wird, lautet:

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

Und es gibt einen ähnlichen Beitrag zu SO: Rückgabe einer Binärdatei vom Controller in der ASP.NET-Web-API . Es geht um die Ausgabe einer vorhandenen Datei. Aber ich konnte es nicht mit einem Stream zum Laufen bringen.

Irgendwelche Vorschläge?

Blaise
quelle
1
Dieser Beitrag hat mir geholfen: stackoverflow.com/a/23768883/585552
Greg

Antworten:

199

Anstatt StreamContentals das zurückzukehren Content, kann ich es zum Laufen bringen 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;
}
Blaise
quelle
2
Wenn die obere Hälfte Ihre Frage beantwortet, posten Sie diese bitte nur als Antwort. Die zweite Hälfte scheint eine andere Frage zu sein - stellen Sie dazu eine neue Frage.
gunr2171
3
Hallo, danke fürs Teilen, habe eine einfache Frage (denke ich). Ich habe ein C # -Frontend, das die httpresponsemessage empfängt. Wie extrahiere ich den Stream-Inhalt und stelle ihn zur Verfügung, damit ein Benutzer ihn auf der Festplatte oder etwas anderem speichern kann (und ich kann die eigentliche Datei erhalten)? Vielen Dank!
Ronald
7
Ich habe versucht, eine selbst erstellte Excel-Datei herunterzuladen. Bei Verwendung von stream.GetBuffer () wurde immer ein beschädigtes Excel zurückgegeben. Wenn ich stattdessen stream.ToArray () verwende, wird die Datei ohne Probleme generiert. Hoffe das hilft jemandem.
Afnpires
4
@AlexandrePires Das liegt daran, dass MemoryStream.GetBuffer()tatsächlich der Puffer des MemoryStream zurückgegeben wird, der normalerweise größer als der Inhalt des Streams ist (um das Einfügen effizient zu gestalten). MemoryStream.ToArray()Gibt den auf die Inhaltsgröße abgeschnittenen Puffer zurück.
M. Stramm
19
Bitte hör auf damit. Diese Art des Missbrauchs von MemoryStream führt zu nicht skalierbarem Code und ignoriert den Zweck von Streams vollständig. Denken Sie: Warum wird nicht byte[]stattdessen alles nur als Puffer angezeigt? Ihre Benutzer können Ihre Anwendung leicht ohne Speicher ausführen.
Makhdumi
97

Wenn Sie zurückkehren möchten, IHttpActionResultkönnen Sie dies folgendermaßen tun:

[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;
}
Ogglas
quelle
3
Gutes Update, um den Rückgabetyp IHttpActionResult anzuzeigen. Ein Refaktor dieses Codes wäre das Aufrufen eines benutzerdefinierten IHttpActionResult wie dem unter stackoverflow.com/questions/23768596/…
Josh
Dieser Beitrag zeigt eine ordentliche Implementierung für den einmaligen Gebrauch. In meinem Fall erwies sich die im obigen Link aufgeführte
Hilfsmethode
45

Diese Frage hat mir geholfen.

Versuchen Sie Folgendes:

Controller-Code:

[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;          
}

HTML-Markup anzeigen (mit Klickereignis und einfacher URL):

<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>
aleha
quelle
1
Hier verwenden Sie FileStreamfür eine vorhandene Datei auf dem Server. Es ist ein bisschen anders als MemoryStream. Aber danke für die Eingabe.
Blaise
4
Wenn Sie aus einer Datei auf einem Webserver lesen, stellen Sie sicher, dass Sie die Überladung für FileShare.Read verwenden. Andernfalls kann es zu Ausnahmen bei der Verwendung von Dateien kommen.
Jeremy Bell
Wenn Sie es durch Speicherstrom ersetzen, wird es nicht funktionieren?
Aleha
@JeremyBell es ist nur ein vereinfachtes Beispiel, hier spricht niemand über Produktion und ausfallsichere Version.
Aleha
1
@Blaise Im Folgenden erfahren Sie, warum dieser Code funktioniert, FileStreamaber nicht funktioniert MemoryStream. Es hat im Grunde mit den Streams zu tun Position.
M. Stramm
9

Hier ist eine Implementierung, die den Inhalt der Datei überträgt, ohne ihn zu puffern (das Puffern in Byte [] / MemoryStream usw. kann ein Serverproblem sein, wenn es sich um eine große Datei handelt).

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);
    }
}

Es kann einfach so verwendet werden:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}
Simon Mourier
quelle
Wie würden Sie die Datei nach dem Download löschen? Gibt es Hooks, die benachrichtigt werden müssen, wenn der Download abgeschlossen ist?
Costa
OK, die Antwort scheint darin zu bestehen, ein Aktionsfilterattribut zu implementieren und die Datei in der OnActionExecuted-Methode zu entfernen.
Costa
5
Diesen Beitrag gefunden Risords Antwort: stackoverflow.com/questions/2041717/… . Man kann diese Linie var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);anstelle vonFile.OpenRead(FilePath)
Costa
7

Ich bin mir nicht ganz sicher, welcher Teil schuld ist, aber hier ist, warum MemoryStreames bei Ihnen nicht funktioniert:

Während Sie schreiben, MemoryStreamwird die PositionEigenschaft erhöht . Der Konstruktor von StreamContentberücksichtigt den aktuellen Stream Position. Wenn Sie also in den Stream schreiben und ihn dann weiterleiten StreamContent, beginnt die Antwort mit dem Nichts am Ende des Streams.

Es gibt zwei Möglichkeiten, dies richtig zu beheben:

1) Inhalt erstellen, in Stream schreiben

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

2) In Stream schreiben, Position zurücksetzen, Inhalt erstellen

[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) sieht etwas besser aus, wenn Sie einen neuen Stream haben, 1) ist einfacher, wenn Ihr Stream nicht bei 0 startet

M. Stramm
quelle
Dieser Code bietet tatsächlich keine Lösung für das Problem, da er denselben Ansatz verwendet, der in der Frage erwähnt wurde. Die Frage besagt bereits, dass dies nicht funktioniert, und ich kann das bestätigen. return Ok (neuer StreamContent (Stream)) gibt die JSON-Darstellung von StreamContent zurück.
Dmytro Zakharov
Der Code wurde aktualisiert. Diese Antwort beantwortet tatsächlich die subtilere Frage: Warum funktioniert die einfache Lösung mit FileStream, aber nicht mit MemoryStream? Anstatt wie eine Datei in WebApi zurückgegeben werden soll.
M. Stramm
3

Für mich war es der Unterschied zwischen

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

und

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

Der erste war die Rückgabe der JSON-Darstellung von StringContent: {"Header": [{"Key": "Content-Type", "Value": ["application / octet-stream; charset = utf-8"]}]}

Während der zweite die richtige Datei zurückgab.

Es scheint, dass Request.CreateResponse eine Überladung aufweist, die eine Zeichenfolge als zweiten Parameter verwendet. Dies scheint dazu geführt zu haben, dass das StringContent-Objekt selbst anstelle des eigentlichen Inhalts als Zeichenfolge gerendert wurde.

EnderWiggin
quelle