Возврат двоичного файла из контроллера в ASP.NET Web API - PullRequest
300 голосов
/ 03 марта 2012

Я работаю над веб-сервисом, использующим новый 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"));
}

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

Ответы [ 7 ]

489 голосов
/ 04 марта 2012

Попробуйте использовать простое 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 = …). Веб-API будет распоряжаться потоком для вас.

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

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

128 голосов
/ 02 января 2014

Для 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"/>
8 голосов
/ 29 июля 2014

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

  • В запросе установите заголовок «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 голосов
/ 28 ноября 2014

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

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

6 голосов
/ 03 марта 2012

Используемая вами перегрузка устанавливает перечисление форматировщиков сериализации.Вам необходимо явно указать тип содержимого, например:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
5 голосов
/ 22 января 2019

Для тех, кто использует .NET Core:

Вы можете использовать интерфейс IActionResult в методе контроллера API, например ...

    [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 для файла и расширение будут зависеть от индивидуальных потребностей.

Ссылка: SO Сообщение Ответ от @ NKosi

3 голосов
/ 01 августа 2012

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

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...