Как вернуть файл с помощью веб-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;
Ogglas
источник
@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, повысит качество вашего ответа.
Адриан Моул,