Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In ASP.NET Core, return a PDF as a file result with the media type application/pdf. Use File(byte[], ...) when the PDF is already in memory, or File(Stream, ...) when it is available as a stream. For a Minimal API, use TypedResults.File(...). These framework results send PDF bytes as a file response rather than encoding them inside JSON. Microsoft’s Minimal API response guidance shows the Minimal API form, and the ControllerBase.File API reference documents the controller overloads.

Return an in-memory PDF from a controller

If your report generator has already produced a byte[], return it directly through the controller’s File method:

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
    [HttpGet("monthly")]
    public IActionResult GetMonthlyReport()
    {
        byte[] pdf = GenerateReport();
        return File(pdf, "application/pdf", "monthly-report.pdf");
    }

    private static byte[] GenerateReport()
    {
        // Replace this with your PDF-generation code.
        return System.IO.File.ReadAllBytes("monthly-report.pdf");
    }
}

The byte-array overload creates a FileContentResult. The second argument identifies the response content type; the third supplies a suggested filename. Microsoft documents these as file-result parameters, not a guarantee that every client will display or save the response in exactly the same way.

In this example, a client requests GET /api/reports/monthly. ASP.NET Core writes the PDF bytes as the response body. Do not wrap those bytes in a JSON object or convert them to Base64 unless your API contract specifically requires a JSON representation: the file result already provides an HTTP file response.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Return a PDF from a stream

When the PDF source naturally provides a stream, return the stream overload instead of first copying the entire document into a byte array:

[HttpGet("download")]
public IActionResult DownloadReport()
{
    Stream pdfStream = OpenPdfStream();
    return File(pdfStream, "application/pdf", "report.pdf");
}

This overload creates a FileStreamResult. The stream must remain open and usable until ASP.NET Core has written the response. The controller API reference states that the supplied stream is disposed after the response is sent, so do not dispose it before returning the result.

That lifecycle makes this pattern incorrect:

public IActionResult DownloadReport()
{
    using var stream = OpenPdfStream();
    return File(stream, "application/pdf", "report.pdf");
}

The using scope ends as the action returns, before the framework has finished sending the response. Let the file result manage the supplied stream’s disposal after transmission, or otherwise ensure the stream remains alive for the response-writing phase.

Choose bytes or a stream

PDF source Use Result type Important consideration
Completed PDF already held in a byte[] File(byte[], "application/pdf", filename) FileContentResult Pass the completed bytes and the PDF media type.
PDF provided by a stream File(Stream, "application/pdf", filename) FileStreamResult Keep the stream available until the framework has sent the response; it is disposed after sending.

The documentation establishes the corresponding result types and stream lifecycle; it does not set a universal document-size threshold at which one approach becomes preferable. Choose according to the representation you already have and the lifecycle you can safely maintain.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Return a PDF from a Minimal API

For an ASP.NET Core Minimal API, use TypedResults.File with the PDF bytes:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/report", () =>
{
    byte[] pdf = System.IO.File.ReadAllBytes("monthly-report.pdf");
    return TypedResults.File(pdf, "application/pdf", "report.pdf");
});

app.Run();

Use TypedResults.File in a Minimal API and ControllerBase.File in a controller action. Both patterns return a framework file response; select the one that matches how the endpoint is built.

Set the content type and filename deliberately

For PDF output, use application/pdf. This tells clients what representation the endpoint is returning. A filename such as report.pdf is a suggested name for the file result. Whether a particular browser or client displays the PDF inline or prompts the user to save it can depend on client behavior; the cited Microsoft API guidance does not establish one universal outcome. Test the response with the clients your API supports.

Keep the filename under your control. If it includes user-provided text, validate and normalize that text before including it in a response. The file-result filename is response metadata, not a safe place to pass unchecked input.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Enable range processing only when needed

The controller file API has overloads with an enableRangeProcessing argument. When enabled, the documented behavior includes partial-content responses for supported byte ranges and a 416 Range Not Satisfiable response for an invalid range. Range processing is optional; ordinary PDF responses do not require it.

For example, the byte-array overload can be called with the range option explicitly set:

return File(
    pdf,
    "application/pdf",
    "report.pdf",
    enableRangeProcessing: true);

Enable this only if your endpoint needs to support range requests, such as clients that request parts of a resource. The existence of the option does not mean every client will make range requests or that every endpoint needs partial responses.

When a stored PDF should be served as a file

The controller API also documents file results based on virtual or physical paths. Microsoft’s Minimal API guidance notes that serving paths this way is less common because static-files middleware usually handles static files. A file result can still make sense when the file must pass through endpoint routing, authorization, or application logic; for a public static PDF, consider whether static file serving is the simpler fit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common PDF response problems

  • The client receives JSON or unreadable text: Check that the action returns a file result and that the PDF bytes are not being serialized as an ordinary object. Set the content type to application/pdf.
  • The stream fails or is empty during transmission: Check its lifetime. Do not close or dispose it before the result has finished writing the response.
  • The client uses an unexpected filename: Check the filename supplied to the file-result overload. It is a suggested filename, and client behavior may vary.
  • A range request does not return partial content: Check whether you used a controller overload that enables range processing. The feature is optional and should not be assumed to be on for every file response.
  • A physical-path endpoint is more complicated than expected: If the PDF is simply a static public file, assess whether static file middleware is a better fit than a controller or Minimal API file result.

Or skip the browser setup

If what you need is a clean screenshot or PDF capture of a webpage—not a response for a PDF generated by your own application—ScreenshotNeo is a separate website screenshot API and MCP server. Its one-call HTTP API accepts a URL and returns a screenshot or PDF. This does not replace the ASP.NET Core file-result patterns above for returning your own PDF bytes.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for integration details. Its clean-shot steps can accept consent banners and remove known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses indicate the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan.

Frequently Asked Questions

What result type does a controller return for a byte-array PDF?

The byte-array file overload returns a FileContentResult.

What result type does a controller return for a stream-backed PDF?

The stream file overload returns a FileStreamResult.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.