Як повернути файл за допомогою веб-API?


98

Я використовую веб-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
@ Роберт: Дякую - тег робить це чіткішим, хоча посилання на "веб-API ASP.NET" все-таки було б зрозумілішим. Частково провина МС за смітливо узагальнене ім'я :)
— Джон Скіт


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

Відповіді:


170

Краще повернути 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 з коментаря patridge : Якщо хтось інший прийде сюди і хоче надіслати відповідь із байтового масиву замість фактичного файлу, ви захочете використовувати новий ByteArrayContent (someData) замість StreamContent (див. Тут ).


1
Перша справа - цей код спричинить виняток, оскільки ви створюєте два об'єкти FileStream, спрямовані на той самий файл. По-друге, ви не хочете використовувати оператор "Використання", тому що як тільки змінна виходить за межі області дії .NET розпоряджається нею, і ви отримуватимете повідомлення про помилку про те, що базове з'єднання закрито.
— Брендон Монтгомері,

48
Якщо хтось інший прийде сюди і хоче надіслати відповідь із байтового масиву замість фактичного файлу, ви захочете використовувати його new ByteArrayContent(someData)замість StreamContent(див. Тут ).
— patridge

Можливо, ви захочете замінити базовий dispose (), щоб ви могли правильно обробляти свої ресурси, коли фреймворк це викликає.
— Філ Купер,

1
Я хотів би зауважити, що правильне значення MediaTypeHeaderValue має вирішальне значення, і, якщо у вас різні типи файлів, ви можете зробити це динамічним. (де fileName - це рядок і має тип файлу, який закінчується як .jpg, .pdf, docx тощо.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new 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 Perfect ... але у багатьох випадках вам не потрібно мати справу з великими файлами. Але я цілком погоджуюся з вашою думкою!
— Андре де Маттос Ферраз

14

Приклад з 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.
— Огглас

Дякую за ваш допис. 'HttpResponseMessage' не працював для мене всередині ApiController, тож ви мене врятували.
— Макс.

13

Лише примітка для .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. Ви можете перетворити його на потік, якщо це потрібно.

Я використав це для повернення файлів, що зберігаються у варбінарному стовпці бази даних.

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

Коротке пояснення того, як ваш код вирішує проблему (-и) операційної системи, покращить якість вашої відповіді.
— Адріан Моул
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.