Как вернуть файл с помощью веб-API?


100

Я использую веб-API ASP.NET .
Я хочу загрузить PDF-файл с C # из API (который генерирует API).

Могу ли я просто вернуть API byte[]? а для приложения C # я могу просто сделать:

byte[] pdf = client.DownloadData("urlToAPI");? 

и

File.WriteAllBytes()?

«Веб-API»? Что именно ты имеешь ввиду? Прочтите tinyurl.com/so-hints и отредактируйте свой вопрос.
— Джон Скит,

1
@JonSkeet: веб-API - это новая функция в последней версии ASP.NET. См. Asp.net/whitepapers/mvc4-release-notes#_Toc317096197
— Роберт Харви,

1
@Robert: Спасибо - тег делает его более понятным, хотя ссылка на «веб-API ASP.NET» была бы еще яснее. Частично MS виноват и в бессмысленном родовом имени :)
— Джон Скит,


Любой, кто хочет вернуть поток через веб-api и IHTTPActionResult, может посмотреть здесь: nodogmablog.bryanhogan.net/2017/02/…
— IbrarMumtaz

Ответы:


172

Лучше вернуть HttpResponseMessage с StreamContent внутри него.

Вот пример:

public HttpResponseMessage GetFile(string id)
{
    if (String.IsNullOrEmpty(id))
        return Request.CreateResponse(HttpStatusCode.BadRequest);

    string fileName;
    string localFilePath;
    int fileSize;

    localFilePath = getFileFromID(id, out fileName, out fileSize);

    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentDisposition.FileName = fileName;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

    return response;
}

UPD из комментария патриджа : если кто-то еще придет сюда, чтобы отправить ответ из байтового массива вместо фактического файла, вы захотите использовать новый ByteArrayContent (someData) вместо StreamContent (см. Здесь ).


1
Во-первых, этот код вызовет исключение, поскольку вы создаете два объекта FileStream, указывающих на один и тот же файл. Во-вторых, вы не хотите использовать оператор «Using», потому что, как только переменная выходит за пределы области видимости, .NET удалит ее, и вы получите сообщения об ошибках о закрытии базового соединения.
— Брэндон Монтгомери,

48
Если кто-то еще хочет отправить ответ из массива байтов вместо фактического файла, вы захотите использовать new ByteArrayContent(someData)вместо StreamContent(см. Здесь ).
— patridge

Вы также можете захотеть переопределить базовую функцию dispose (), чтобы вы могли правильно обрабатывать свои ресурсы, когда платформа вызывает ее.
— Фил Купер

2
Я хотел бы отметить, что правильный MediaTypeHeaderValue имеет решающее значение, и чтобы сделать его динамичным, если у вас есть разные типы файлов, вы можете сделать это. (где имя_файла является строкой и имеет тип файла, заканчивающийся как .jpg, .pdf, docx и т. д.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = новый MediaTypeHeaderValue (contentType);
— JimiSweden

1
Удаляется ли FileStream автоматически?
— Брайан

37

Я сделал следующее действие:

[HttpGet]
[Route("api/DownloadPdfFile/{id}")]
public HttpResponseMessage DownloadPdfFile(long id)
{
    HttpResponseMessage result = null;
    try
    {
        SQL.File file = db.Files.Where(b => b.ID == id).SingleOrDefault();

        if (file == null)
        {
            result = Request.CreateResponse(HttpStatusCode.Gone);
        }
        else
        {
            // sendo file to client
            byte[] bytes = Convert.FromBase64String(file.pdfBase64);


            result = Request.CreateResponse(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = file.name + ".pdf";
        }

        return result;
    }
    catch (Exception ex)
    {
        return Request.CreateResponse(HttpStatusCode.Gone);
    }
}

Это действительно отвечает на вопрос
— Мик

1
Это не лучший вариант для больших файлов, поскольку при этом в память загружается все изображение. Лучше вариант с потоком.
— Пол Риди,

@PaulReedy Идеально ... но во многих случаях вам не нужно иметь дело с большими файлами. Но я полностью согласен с вашей точкой зрения!
— Андре де Маттос Феррас

15

Пример с IHttpActionResultin ApiController.

[HttpGet]
[Route("file/{id}/")]
public IHttpActionResult GetFileForCustomer(int id)
{
    if (id == 0)
      return BadRequest();

    var file = GetFile(id);

    IHttpActionResult response;
    HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);
    responseMsg.Content = new ByteArrayContent(file.SomeData);
    responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
    responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    response = ResponseMessage(responseMsg);
    return response;
}

Если вы не хотите загружать PDF-файл и использовать браузер, встроенный в программу просмотра PDF-файлов, удалите следующие две строки:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;

@ElbertJohnFelipe Да, вы получите файл var file = GetFile(id);. file.SomeDataявляется байтовым массивом ( byte[]) и file.FileNameявляется string.
— Ogglas

Спасибо за ваше сообщение. «HttpResponseMessage» не работал у меня внутри ApiController, поэтому вы меня спасли.
— Макс

14

Просто примечание .Net Core: мы можем использовать FileContentResultи установить для contentType значение, application/octet-streamесли мы хотим отправлять необработанные байты. Пример:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}

1
Это отлично работает. Также, если вы хотите контролировать имя файла, есть свойство, FileContentResultвызываемое FileDownloadNameдля указания имени файла
— weeksdev

@weeksdev ах не знал этого. Спасибо за комментарий.
— Амир Ширази,

Вот и все, спасибо. Также очень полезен комментарий от weeksdev.
— fragg

1

Мне было интересно, есть ли простой способ загрузить файл более ... "общим" способом. Я это придумал.

Это простой ActionResultспособ загрузить файл из вызова контроллера, который возвращает файл IHttpActionResult. Файл хранится в формате byte[] Content. При необходимости вы можете превратить его в поток.

Я использовал это для возврата файлов, хранящихся в столбце varbinary базы данных.

    public class FileHttpActionResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string FileName { get; set; }
        public string MediaType { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public byte[] Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = new HttpResponseMessage(StatusCode);

            response.StatusCode = StatusCode;
            response.Content = new StreamContent(new MemoryStream(Content));
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = FileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaType);

            return Task.FromResult(response);
        }
    }

Краткое объяснение того, как ваш код устраняет проблемы OP, повысит качество вашего ответа.
— Адриан Моул,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.