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

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

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

6 ответов

Решение

Попробуйте использовать простой 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.

Для 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"/>

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

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

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

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

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

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

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

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

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

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

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

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

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

Вы могли бы попробовать

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

Вы можете попробовать следующий фрагмент кода

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

Надеюсь, это сработает для вас.

Другие вопросы по тегам