Возврат двоичного файла из контроллера в ASP.NET Web API

303

Я работаю над веб-сервисом, используя новый WebAPI ASP.NET MVC, который будет обслуживать двоичные файлы, в основном файлы .cab и .exe.

Кажется, что работает следующий метод контроллера, что означает, что он возвращает файл, но он устанавливает тип содержимого 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"));
}

Есть ли лучший способ сделать это?

  • 0
    Любой, кто приземлится, желая вернуть байтовый массив через поток через web api и IHTTPActionResult, тогда увидит здесь: nodogmablog.bryanhogan.net/2017/02/…
Теги:
asp.net-mvc
asp.net-web-api

7 ответов

444
Лучший ответ

Попробуйте использовать простой HttpResponseMessage со свойством Content, установленным в 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;
}

Несколько вещей, которые следует отметить в stream:

  • Вы не должны вызывать stream.Dispose(), так как веб-API по-прежнему должен иметь доступ к нему, когда он обрабатывает метод контроллера result для отправки данных обратно клиенту. Поэтому не используйте блок using (var stream = …). Web API предоставит вам поток.

  • Убедитесь, что в потоке установлена ​​текущая позиция 0 (то есть начало данных потока). В приведенном выше примере это заданное, поскольку вы только что открыли файл. Однако в других сценариях (например, когда вы сначала записываете некоторые двоичные данные в MemoryStream), убедитесь, что stream.Seek(0, SeekOrigin.Begin); или установите stream.Position = 0;

  • С файловыми потоками явное указание разрешения FileAccess.Read может помочь предотвратить проблемы с правами доступа на веб-серверах; Учетным пулам приложений IIS часто присваиваются права доступа только для чтения/списка/выполнения для wwwroot.

  • 37
    Вы случайно не знаете, когда поток закроется? Я предполагаю, что инфраструктура в конечном итоге вызывает HttpResponseMessage.Dispose (), которая, в свою очередь, вызывает HttpResponseMessage.Content.Dispose (), эффективно закрывая поток.
  • 41
    Стив - вы правы, и я подтвердил, добавив точку останова в FileStream.Dispose и запустив этот код. Фреймворк вызывает HttpResponseMessage.Dispose, который вызывает StreamContent.Dispose, который вызывает FileStream.Dispose.
Показать ещё 12 комментариев
111

Для Web API 2 вы можете реализовать IHttpActionResult. Вот мой:

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

Тогда как-то так в вашем контроллере:

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

И вот один из способов, которым вы можете сказать IIS игнорировать запросы с расширением, чтобы запрос направлялся в контроллер:

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
  • 0
    будет ли приведенный выше код работать для скачивания zip файла
  • 0
    Нет, извините, это не работает для почтовых файлов.
Показать ещё 17 комментариев
8

В то время как предлагаемое решение работает нормально, есть еще один способ вернуть массив байтов из контроллера, при этом правильно отредактированный поток ответов:

  • В запросе установите заголовок "Accept: application/octet-stream".
  • На стороне сервера добавьте форматировщик типа мультимедиа для поддержки этого типа mime.

К сожалению, WebApi не включает форматтера для "application/octet-stream". В GitHub есть реализация: BinaryMediaTypeFormatter (есть небольшие адаптации, чтобы заставить его работать для webapi 2, изменены сигнатуры методов).

Вы можете добавить этот форматтер в свою глобальную конфигурацию:

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

Теперь WebApi должен использовать BinaryMediaTypeFormatter, если запрос указывает правильный заголовок Accept.

Я предпочитаю это решение, потому что контроллер возврата возвращаемого байта [] более удобен для тестирования. Хотя другое решение позволяет вам больше контролировать, если вы хотите вернуть другой тип контента, чем "application/octet-stream" (например, "image/gif" ).

7

Для тех, у кого проблема API, вызываемая более одного раза при загрузке довольно большого файла с использованием метода в принятом ответе, установите для буферизации ответа значение true   System.Web.HttpContext.Current.Response.Buffer = true;

Это гарантирует, что весь двоичный контент буферизуется на стороне сервера, прежде чем он будет отправлен клиенту. В противном случае вы увидите, что к контроллеру отправляется несколько запросов, и если вы не будете их обрабатывать должным образом, файл будет поврежден.

  • 2
    Свойство Buffer устарело в пользу BufferOutput . По умолчанию это true .
6

Перегрузка, которую вы используете, задает перечисление форматов сериализации. Вам нужно указать тип содержимого явно:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
  • 2
    Спасибо за ответ. Я попробовал это, и я все еще вижу Content Type: application/json в Fiddler. Content Type по-видимому, установлен правильно, если я httpResponseMessage перед возвратом ответа httpResponseMessage . Есть еще идеи?
3

Вы можете попробовать

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

Для тех, кто использует .NET Core, вы можете использовать интерфейс IActionResult, например:

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

Этот пример упрощен, но должен понять суть. В .NET Core этот процесс намного проще, чем в предыдущих версиях .NET, то есть без настройки типа ответа, содержимого, заголовков и т.д.

Также, конечно, тип MIME для файла и расширение будут зависеть от индивидуальных потребностей.

Ссылка: ТАК Сообщение от @NKosi

Ещё вопросы

Сообщество Overcoder
Наверх
Меню