Asp net core вернуть файл
При обращении к веб-приложению, как правило, пользователь ожидает получить некоторый ответ, например, в виде веб-страницы, которая наполнена данными. На стороне сервера метод контроллера, получая параметры и данные запроса, обрабатывает их и формирует ответ в виде результата действия. Результат действия - это тот объект, который возвращается методом после обработки запроса.
Результатом действия может быть практически что угодно. Например, в прошлых темах использовался объект string, например:
Пользователь передает методу некоторые значения и ответ на запрос видит в своем браузере строку ответа.
Результатом действия может быть какой-нибудь сложный объект:
В данном случае использовался класс Geometry из прошлой темы.
Результатом может быть даже void , то есть по сути ничего:
В данном случае метод GetVoid представляет вполне реальное действие контроллера, к которому можно обращаться из адресной строки браузера, передавать параметры, и который может содержать сложную логику обработки запроса. Только после обращения к этому методу пользователь увидит в своем веб-браузере одну пустоту, так как метод ничего не возвращает.
Но в большинстве случаев мы будем иметь дело не с void и даже не с типом string, а с объектами типа IActionResult , которые непосредственно предназначены для генерации результата действия. Интерфейс IActionResult находится в пространстве имен Microsoft.AspNetCore.Mvc и определяет один метод:
Метод ExecuteResultAsync() принимает контекст действия и выполняет генерацию результата.
Этот интерфейс затем реализуется абстрактным базовым классом ActionResult :
ActionResult добавляет синхронный метод, который выполняется в асинхронном. И если мы вдруг захотим создать свой класс результата действий, то как раз можем либо унаследовать его от ActionResult, либо реализовать интерфейс IActionResult.
Итак, создадим свой класс результата действий. Для этого вначале добавим в проект папку Util , в которой будет храниться классы результатов. Затем в эту папку добавим новый класс, который назовем HtmlResult :
Теперь используем этот класс в контроллере:
ContentResult : пишет указанный контент напрямую в ответ в виде строки
EmptyResult : отправляет пустой ответ в виде статусного кода 200
Аналогичен следующему методу:
NoContentResult : во многом похож на EmptyResult, также отправляет пустой ответ, только в виде статусного кода 204
FileResult : является базовым классом для всех объектов, которые пишут набор байтов в выходной поток. Предназначен для отправки файлов
FileContentResult : класс, производный от FileResult , пишет в ответ массив байтов
VirtualFileResult : также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути
PhysicalFileResult : также производный от FileResult класс, пишет в ответ файл, находящийся по заданному пути. Только в отличие от предыдущего класса использует физический путь, а не виртуальный.
FileStreamResult : класс, производный от FileResult , пишет бинарный поток в выходной ответ
ObjectResult : возвращает произвольный объект, как правило, применяется в качестве базового класса для других классов результатов. Но можно применять и самостоятельно:
BadRequestResult : производный от StatusCodeResult . Возвращает статусный код 400, тем самым указывая, что запрос некорректен
BadRequestObjectResult : производный от ObjectResult . Возвращает статусный код 400 с некоторой дополнительной информацией
OkResult : производный от StatusCodeResult . Возвращает статусный код 200, который уведомляет об успешном выполнении запроса
OkObjectResult : производный от ObjectResult . Возвращает статусный код 200 с некоторой дополнительной информацией
CreatedResult : возвращает статусный код 201, который уведомляет о создании нового ресурса. В качестве параметра принимает адрес нового ресурса
CreatedAtActionResult : возвращает статусный код 201, который уведомляет о создании нового ресурса. В качестве параметра принимает название метода и контроллера, а также параметров запроса, которые вместе создают адрес нового ресурса
CreatedAtRouteResult : возвращает статусный код 201, который уведомляет о создании нового ресурса. В качестве параметра принимает название маршрута, который используется для создания адреса нового ресурса
AcceptedResult : возвращает статусный код 202
AcceptedAtActionResult : возвращает статусный код 202. В качестве параметра принимает название метода и контроллера, а также параметров запроса
AcceptedAtRouteResult : возвращает статусный код 202. В качестве параметра принимает название и параметры маршрута
ChallengeResult : используется для проверки аутентификации пользователя
JsonResult : возвращает в качестве ответа объект или набор объектов в формате JSON
PartialViewResult : производит рендеринг частичного представления в выходной поток
RedirectResult : перенаправляет пользователя по другому адресу URL, возвращая статусный код 302 для временной переадресации или код 301 для постоянной переадресации зависимости от того, установлен ли флаг Permanent .
RedirectToRouteResult : класс работает подобно RedirectResult , но перенаправляет пользователя по определенному адресу URL, указанному через параметры маршрута
RedirectToActionResult : выполняет переадресацию на определенный метод контроллера
RedirectToPageResult : выполняет переадресацию на определенную странцу Razor (относится к подсистеме RazorPages)
LocalRedirectResult : перенаправляет пользователя по определенному адресу URL в рамках веб-приложения
ViewComponentResult : возвращает в ответ сущность ViewComponent
ViewResult : производит рендеринг представления и отправляет результаты рендеринга в виде html-страницы клиенту
In a regular MVC controller, we can output pdf with a FileContentResult .
But how can we change it into an ApiController ?
Here is what I've tried but it doesn't seem to work.
The returned result displayed in the browser is:
7 Answers 7
Instead of returning StreamContent as the Content , I can make it work with ByteArrayContent .
If the top half answers your question, please only post that as an answer. The second half appears to be a different question - post a new question for that.
I was trying to download a self generated excel file. Using the stream.GetBuffer() always returned an corrupted excel. If instead I use stream.ToArray() the file is generated without a problem. Hope this helps someone.
@AlexandrePires That is because MemoryStream.GetBuffer() actually returns the MemoryStream's buffer, which is usually larger than the stream's content (to make insertions efficient). MemoryStream.ToArray() returns the buffer truncated to the content size.
Please stop doing this. This sort of abuse of MemoryStream causes, unscalable code and completely ignores the purpose of Streams. Think: why isn't everything just exposed as byte[] buffers instead? Your users can easily run your application out of memory.
This post demonstrates a nice tidy single use implementation. In my case, the helper method listed in the above link proved more helpful
I think you can only use stream.GetBuffer() if you specify the actual size using the ByteArrayContent(Byte[], Int32, Int32) constructor: new ByteArrayContent(stream.GetBuffer(), 0, checked((int)stream.Length)) . The internal buffer array may be longer than the actual contents so specifying the content length is necessary.
This question helped me.
View Html markup (with click event and simple url):
Here you are using FileStream for an existing file on the server. It is a bit different from MemoryStream . But thanks for the input.
If you do read from a file on a web server, be sure to use the overload for FileShare.Read, otherwise you may encounter file in use exceptions.
@JeremyBell it is just a simpified example, nobody talks here about production and fail-safe version.
@Blaise See below for why this code works with FileStream but fails with MemoryStream . It's basically got to do with the Stream's Position .
Here is an implementation that streams the file's content out without buffering it (buffering in byte[] / MemoryStream, etc. can be a server problem if it's a big file).
It can be simply used like this:
How would you delete the file after the download is done? Are there any hooks to be notified when the download is finished?
ok, the answer seems to be to implement an action filter attribute and remove the file in the OnActionExecuted method.
I am not exactly sure which part to blame, but here's why MemoryStream doesn't work for you:
As you write to MemoryStream , it increments its Position property. The constructor of StreamContent takes into account the stream's current Position . So if you write to the stream, then pass it to StreamContent , the response will start from the nothingness at the end of the stream.
There's two ways to properly fix this:
construct content, write to stream
write to stream, reset position, construct content
looks a little better if you have a fresh Stream, 1) is simpler if your stream does not start at 0
VirtualFileResult
VirtualFileResult работает похожим образом, только возвращает файл по виртуальному пути. Здесь надо учитывать, что по умолчанию все пути к файлам в данном случае будут сопоставляться с папкой wwwroot. То есть нам надо помещать папки с файлами или отдельные файлы в каталог wwwroot:
В данном случае предполагается, что файл "hello.txt" располагается в папке "wwwroot/Files/".
Во всех выше перечисленных случаях использование имени файла в качестве третьего параметра метода File/PhysicalFile необязательно. А вот тип файла обязательно надо передавать. Но подобное поведение может быть не всегда удобным: мы можем точно не знать тип отправляемых файлов, или файлы представляют самые разные типы. И в этом случае мы можем использовать универсальный тип application/octet-stream .
Все ранее рассмотренные методы OnGet и OnPost, предназначенные для обработки GET и POST-запросов, ничего не возвращали, то есть имели в качестве возвращаемого типа тип void и просто выполняли некоторое действие:
В реальности данные методы по умолчанию возвращали связанную с классом страницу Razor. Однако иногда возникает необходимость возвратить некоторый результат, например, статусный код ошибки или сделать редирект. И подобные методы, как и методы контроллеров, также могут возвращать объекты IActionResult. Для этого в классе PageModel определено ряд методов:
Content() возвращает объект ContentResult, то есть фактически некоторое текстовое содержимое
File() возвращает с помощью различных перегруженных версий объекты FileContentResult/FileStreamResult/VirtualFileResult, то есть отправляет клиенту файл
Forbid() возвращает статусный код 403
LocalRedirect()/LocalRedirectPermanent() выполняет переадресацию по определенному локальному адресу
NotFound() возвращает статусный код 404
PhysicalFile() возвращает файл по физическому пути
Page() возвращает объект PageResult или фактически текущую страницу Razor
Redirect()/RedirectPermanent() выполняет переадресацию по определенному адресу
RedirectToAction()/RedirectToActionPermanent() выполняет переадресацию на определенное действие контроллера
RedirectToPage()/RedirectToPagePermanent() выполняет переадресацию на определенную страницу Razor
RedirectToRoute()/RedirectToRoutePermanent() выполняет переадресацию по определенному маршруту
StatusCode() возвращает объект StatusCodeResult, то есть посылает статусный код
Unauthorized() возвращает объект UnauthorizedResult, то есть статусный код ошибки 401
Большинство этих методов и возвращаемых ими результатов было рассмотрено ранее в теме Результаты действий контроллеров и ряде последующих статей. В данном случае рассмотрим некоторые моменты, которые могут вызвать трудности. Например, в зависимости от результата надо возвратить либо статусный код ошибки, либо страницу Razor.
Например, пусть у нас есть класс Person, который представляет данные:
Допустим, мы хотим в PageModel получить из списка по имени нужный объект Person:
Метод OnGet возвращает IActionResult. В зависимости от того, найден ли пользователь, этот IActionResult будет представлять либо NotFoundResult, либо PageResult. Причем метод Page() возвращает текущую страницу, как если бы у нас был бы просто определен метод OnGet с типом void:
Code so far
Загрузка массива байтов
Похожим образом работает и класс FileContentResult, только используется метод File() , а вместо имени файла передается массив байтов, в который был считан файл:
Загрузка файла по пути. PhysicalFileResult
Воспользуемся PhysicalFileResult для отправки файла book.pdf клиенту:
Чтобы получить полный физический путь каталога относительно проекта, воспользуемся сервисом IWebHostEnvironment . Он автоматически передается в контроллер, и через конструктор контроллера мы его можем получить. Свойство ContentRootPath данного сервиса указывает на физический путь к каталогу проекта.
И, при обращении, например, по пути Home/GetFile нам будет предложено сохранить данный файл на локальном компьютере.
6 Answers 6
The framework will dispose of the stream used in this case when the response is completed. If a using statement is used, the stream will be disposed before the response has been sent and result in an exception or corrupt response.
In my case I needed to render an Excel in memory and return it for download, so I needed to define a file name with extension as well: return File(stream, "application/octet-stream", "filename.xlsx"); This way when it downloads the user can open it directly.
@RobL not in this case. the framework will dispose of the stream when the response is completed. If you use a using statement the stream will be disposed before the response has been sent.
The magic behind __get_stream_based_on_id_here__ could be interesting since common functions that return a Stream of a file are not async whereas functions that are async are only returning a byte array etc. Ofc, I could create another Stream from the byte array but I was wondering if there is a solution with one Stream only.
You can return FileResult with this methods:
If within a ControllerBase there are many overloaded versions of ControllerBase.File helper that returns any one of those.
Your answer is still valid. So do not feel disheartened. I was just pointing out some resources you can use to support your answer.
Here is a simplistic example of streaming a file:
Be sure to use FileStreamResult from Microsoft.AspNetCore.Mvc and not from System.Web.Mvc .
You can return a FileContentResult object (Blob) from the server. It'll not get automatically downloaded. You may create an anchor tag in your front-end app programmatically and set the href property to an object URL created from the Blob by the method below. Now clicking on the anchor will download the file. You can set a file name by setting the 'download' attribute to the anchor as well.
Backend
Assume there is a page that lists uploaded user documents by file name, each list item(document) has a download button, the backend is WEB API.
Для отправки клиенту файлов предназначен абстрактный класс FileResult, функционал которого реализуется в классах-наследниках:
FileContentResult : отправляет клиенту массив байтов, считанный из файла
VirtualFileResult : представляет простую отправку файла напрямую с сервера по виртуальному пути
FileStreamResult : создает поток - объект System.IO.Stream, с помощью которого считывает и отправляет файл клиенту
PhysicalFileResult : также отправляет файл с сервера, но для отправки используется реальный физический путь
Во первых трех случаях для отправки файлов применяется метод File() , а для создания объекта PhysicalFileResult используется метод PhysicalFile() . Только в зависимости от выбранного способа используется соответствующая перегруженная версия этого метода.
Для примера добавим в корень проекта папку Files , в которой находится файл book.pdf . И также добавим в папку wwwroot папку Files , в которой находится файл hello.txt :
Отправка потока. FileStreamResult
Если мы хотим возвратить объект FileStreamResult, то в качестве первого аргумента в методе File идет объект Stream для отправляемого файла:
Читайте также: