Як повернути файл за допомогою веб-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.