Zwracanie pliku binarnego z kontrolera w ASP.NET Web API

Pracuję nad serwisem internetowym wykorzystującym ASP.NET nowy WebAPI MVC, który będzie obsługiwał pliki binarne, głównie Pliki .cab i .exe.

Następująca metoda kontrolera wydaje się działać, co oznacza, że zwraca plik, ale ustawia typ zawartości na 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"));
}
Czy jest na to lepszy sposób?
Author: Josh Earl, 2012-03-02

8 answers

Spróbuj użyć prostego HttpResponseMessage z właściwością Content ustawioną na 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;
}

Kilka rzeczy na temat stream używane:

  • Nie wolno wywoływać stream.Dispose(), ponieważ Web API nadal musi mieć dostęp do niego, gdy przetwarza metodę kontrolera result, Aby wysłać dane z powrotem do klienta. Dlatego nie należy używać bloku using (var stream = …). Web API będzie dysponować strumień dla Ciebie.

  • Upewnij się, że strumień ma ustawioną bieżącą pozycję do 0 (tj. początek danych strumienia). W powyższym przykładzie jest to podane, ponieważ dopiero co otworzyłeś plik. Jednak w innych scenariuszach (np. gdy po raz pierwszy zapisujesz dane binarne do MemoryStream), upewnij się, że stream.Seek(0, SeekOrigin.Begin); lub ustawisz stream.Position = 0;

  • Ze strumieniami plików, jawnie określając FileAccess.Read uprawnienia mogą pomóc w zapobieganiu problemom z prawami dostępu na serwerach WWW; konta puli aplikacji IIS są często podawane tylko do odczytu / listy / wykonania praw dostępu do wwwroot.

 527
Author: carlosfigueira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-15 10:40:08

Dla Web API 2 można zaimplementować IHttpActionResult. Oto moje:

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

Wtedy coś takiego w Twoim kontrolerze:

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

I oto jeden ze sposobów, w jaki można powiedzieć IIS, aby ignorował żądania z rozszerzeniem, aby żądanie trafiło do kontrolera:

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
 141
Author: Ronnie Overby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-22 14:37:16

Dla użytkowników. NET Core:

Możesz użyć interfejsu IActionResult w metodzie kontrolera API, w ten sposób...

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

Ten przykład jest uproszczony, ale powinien być zrozumiały. W. Net Core proces ten jest więc znacznie prostszy niż w poprzednich wersjach. NET - tzn. brak ustawiania typu odpowiedzi, zawartości, nagłówków itp.

Również, oczywiście typ MIME dla pliku i rozszerzenia będzie zależał od indywidualnych potrzeb.

Reference: SO Post Answer by @NKosi

 19
Author: KMJungersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Chociaż sugerowane rozwiązanie działa poprawnie, istnieje inny sposób na zwrócenie tablicy bajtów z kontrolera, ze strumieniem odpowiedzi odpowiednio sformatowanym :

  • w żądaniu Ustaw nagłówek "Accept: application / octet-stream".
  • Po stronie serwera, Dodaj program do formatowania typów multimediów, aby obsługiwać ten typ mime.

Niestety, WebApi nie zawiera żadnego formatera dla "application / octet-stream". Istnieje implementacja na Githubie: BinaryMediaTypeFormatter (istnieją drobne adaptacje, aby działało to dla webapi 2, podpisy metod zostały zmienione).

Możesz dodać ten formatter do globalnej konfiguracji:

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

WebApi powinien teraz używać BinaryMediaTypeFormatter, jeśli żądanie określa poprawny nagłówek Accept.

Wolę to rozwiązanie, ponieważ kontroler akcji zwracający bajt [] jest wygodniejszy w testowaniu. Jednak inne rozwiązanie pozwala na większą kontrolę, jeśli chcesz zwrócić inny typ zawartości niż "application / octet-stream" (na przykład "image / gif").

 10
Author: Eric Boumendil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-07 14:06:15

Dla każdego, kto ma problem z wywołaniem API więcej niż jeden raz podczas pobierania dość dużego pliku przy użyciu metody w zaakceptowanej odpowiedzi, Ustaw buforowanie odpowiedzi na true System.Www.HttpContext.Aktualne.Odpowiedź.Bufor = true;

Zapewnia to, że cała zawartość binarna jest buforowana po stronie serwera, zanim zostanie wysłana do klienta. W przeciwnym razie do kontrolera zostanie wysłanych wiele żądań, a jeśli nie obsłużysz ich poprawnie, plik zostanie stać się skorumpowanym.

 9
Author: JBA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-13 11:54:33

Przeciążenie, którego używasz, ustawia wyliczenie formaterów serializacji. Musisz określić typ zawartości jawnie jak:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
 7
Author: David Peden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-02 22:58:02

Możesz spróbować]}

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
 3
Author: MickySmig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-01 15:23:01

Możesz wypróbować następujący fragment kodu

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Mam nadzieję, że ci się uda.
 0
Author: Ajay Prajapati,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-29 16:30:11