# ASP.NET Core Printing: Send PDFs to Local Printers

> ASP.NET Core printing without Windows-only APIs: send PDFs from Linux or Windows servers to local printers, with C# examples and safe retry handling.

Published 2026-11-02 · https://printscaler.com/blog/aspnet-core-printing-local-printers

![Cloud application sending a PDF through a connected workstation to an office printer](https://printscaler.com/blog/images/aspnet-core-printing-local-printers-8.jpg)

ASP.NET Core printing to a user's local printer requires a connection between your server and the computer that can access that printer. Your application can submit a PDF through PrintScaler's HTTPS API, and the PrintScaler agent delivers it to the selected physical printer. This works whether your ASP.NET Core application runs on Windows, Linux, or in a container, without opening inbound ports at the printing location.

## Why ordinary server-side printing does not reach local printers

A printer installed on a warehouse workstation is not automatically available to an ASP.NET Core application running in a data center. The application and the printer belong to different operating-system sessions, networks, and security boundaries.

Calling a local printing API on your web server does not change that. At best, it reaches printers accessible to that server—not the USB label printer or office laser printer attached to a customer's computer.

There is also a platform issue. `System.Drawing.Printing` is a Windows-oriented printing API, not a cross-platform bridge to remote printers. Moving an application into a Linux container does not bring Windows printer drivers or workstation printer queues along with it.

Treat these as separate responsibilities:

1. **Document generation:** Create the invoice, packing slip, or report as a PDF.
2. **Print routing:** Decide which physical printer should receive it.
3. **Local delivery:** Use software at the destination to submit the document to that printer.

You can keep your existing PDF generation library. The missing component is usually delivery, not document rendering.

## Choose the right ASP.NET Core printing architecture

The right approach depends on whether a person should review the document and where the printer is connected.

| Approach | Appropriate use | Main limitation |
|---|---|---|
| Return a PDF to the browser | A user downloads or manually prints a document | Requires user interaction and printer selection |
| Print through the server's operating system | A controlled Windows installation with server-accessible printers | Does not automatically reach remote workstation printers |
| Submit a print request through an API and local agent | A hosted application printing at offices, warehouses, or branches | Requires an agent-connected destination computer |

For a typical hosted business application, the third approach separates your application deployment from printer installation.

The path looks like this:

```text
ASP.NET Core application
    -> PrintScaler HTTPS API
    -> PrintScaler agent at the destination
    -> Local operating-system print queue
    -> Physical printer
```

The agent establishes an outbound WebSocket connection. You do not need to expose Windows printer sharing, forward a printer port, or connect every customer's network to your application server.

If your actual requirement is removing a browser print dialog, see the related guide to [silent printing from a browser](https://printscaler.com/blog/silent-printing-from-browser). Here, the focus is the C# backend that creates and routes print requests.

## Set up PrintScaler for your .NET application

### 1. Install the agent where the printer is available

Install the [PrintScaler agent](https://printscaler.com/download) on a Windows, macOS, or Linux computer that can already print to the destination printer. Raspberry Pi is also supported.

Before troubleshooting your application, print a document from that computer using its normal operating-system tools. If that fails, resolve the driver, connection, or printer configuration first.

Next:

1. Open the PrintScaler dashboard.
2. Link the computer by entering the pairing code in the dashboard.
3. Find the reported printer.
4. Use the dashboard's test page button.
5. Record the printer ID for your application configuration.

The agent starts automatically after reboots and updates itself. However, the destination computer still needs power, a working network connection, and access to the printer.

### 2. Generate a PDF your application can deliver

PrintScaler accepts PDF content in two forms:

- `pdf_uri`: A URL from which the PDF can be retrieved.
- `pdf_base64`: The PDF bytes encoded as Base64 in the request.

Use `pdf_uri` when your application already stores generated documents in object storage. The URL must be accessible for retrieval; a URL that only works inside an authenticated employee browser session is not sufficient.

Use `pdf_base64` when you already have the bytes in memory and prefer to send them directly. Base64 increases the payload size, so account for that when handling large reports or high concurrency.

For URI-based printing, use a document-specific URL rather than a mutable address such as `/latest-invoice.pdf`. Otherwise, a delayed retrieval could fetch a different version of the document.

### 3. Keep credentials in server-side configuration

Create an API key in the dashboard and keep it in your ASP.NET Core backend. Do not put it in JavaScript, a mobile application bundle, a PDF URL, or a source-code repository.

For local development, initialize ASP.NET Core user secrets in your project:

```bash
dotnet user-secrets init
dotnet user-secrets set "PrintScaler:ApiKey" "psk_REPLACE_WITH_YOUR_KEY"
```

For deployment, use your hosting platform's secret configuration. ASP.NET Core can read the same setting from an environment variable named `PrintScaler__ApiKey`.

The examples below use bearer authentication. API-key-as-Basic-username authentication is also supported; consult the [API authentication reference](https://printscaler.com/docs#authentication) for the full request requirements.

## Implement a typed C# printing client

A typed `HttpClient` keeps authentication, transport settings, and request construction out of your controllers. The following example uses standard ASP.NET Core HTTP client registration and sends the documented print-job payload.

Add this registration to your existing `Program.cs`, before `builder.Build()`:

```csharp
using System.Net.Http.Headers;

var apiKey = builder.Configuration["PrintScaler:ApiKey"]
    ?? throw new InvalidOperationException(
        "PrintScaler:ApiKey is not configured.");

builder.Services.AddHttpClient<PrintScalerClient>(client =>
{
    client.BaseAddress = new Uri("https://api.printscaler.com/");
    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", apiKey);
    client.Timeout = TimeSpan.FromSeconds(30);
});
```

Then create `PrintScalerClient.cs`:

```csharp
using System.Net.Http.Json;

public sealed class PrintScalerClient
{
    private readonly HttpClient _http;

    public PrintScalerClient(HttpClient http)
    {
        _http = http;
    }

    public async Task SubmitPdfAsync(
        int printerId,
        Uri pdfUri,
        string operationKey,
        string title,
        CancellationToken cancellationToken = default)
    {
        if (printerId <= 0)
            throw new ArgumentOutOfRangeException(nameof(printerId));

        if (!pdfUri.IsAbsoluteUri || pdfUri.Scheme != "https")
            throw new ArgumentException(
                "Use an absolute HTTPS document URL.", nameof(pdfUri));

        ArgumentException.ThrowIfNullOrWhiteSpace(operationKey);

        using var request = new HttpRequestMessage(
            HttpMethod.Post, "v1/printjobs");

        request.Headers.Add("X-Idempotency-Key", operationKey);
        request.Content = JsonContent.Create(new
        {
            printerId,
            contentType = "pdf_uri",
            content = pdfUri.AbsoluteUri,
            title
        });

        using var response = await _http.SendAsync(
            request, cancellationToken);

        response.EnsureSuccessStatusCode();
    }
}
```

The HTTPS-only document check is an application policy in this example, not a claim that it is the API's only accepted URL scheme.

The method deliberately does not invent a response model. For production job tracking, deserialize the documented create-job response and persist the returned identifier according to the API reference.

### Call the client from your application workflow

Inject `PrintScalerClient` into an existing application service, controller, or worker. A call from that workflow might look like this:

```csharp
await printScaler.SubmitPdfAsync(
    printerId: 123,
    pdfUri: new Uri(
        "https://documents.example.com/packing-slips/order-10482.pdf"),
    operationKey: "packing-slip-order-10482-original-v1",
    title: "Packing slip 10482",
    cancellationToken: cancellationToken);
```

Replace the example URL with a retrievable PDF and use the printer ID from your own account.

In a multi-location application, choose the printer on the server from an authorized location-to-printer mapping. Do not trust a browser-supplied printer ID simply because it is a valid integer. The authenticated user must be allowed to print that document at that destination.

Likewise, if your application accepts document URLs, validate their origin or resolve them from your own stored document records. An authenticated print endpoint should not become an unrestricted way to submit arbitrary URLs and print jobs.

## Make retries safe without blocking web requests

The most important failure case is an uncertain result: your application sends a print request, but the connection times out before it receives the response.

The API might already have accepted the request. Blindly sending a new request can produce a duplicate invoice or packing slip.

### Use a stable idempotency key

PrintScaler supports `X-Idempotency-Key` so a retried submission does not create a second print job. Follow the [idempotency documentation](https://printscaler.com/docs#idempotency) when implementing retry behavior.

Your application should create and persist the key for the intended print action, not generate a new random value on every attempt.

For example:

- Original packing slip: one persisted operation key.
- Retry after a network timeout: the same key and same request.
- Deliberate replacement copy: a new operation key.

Do not reuse one key for different documents or destinations. For a changed document, create a distinct print operation rather than mutating an existing request while keeping its key.

Idempotency protects API submission. It is not a guarantee that a printer cannot jam, feed two sheets, or require an operator to resolve a physical problem.

### Use durable background processing for business events

For an order-completed workflow, a durable queue or database-backed outbox is generally safer than a long-running HTTP request:

1. Save the order and a pending print operation.
2. Let a background worker load that operation.
3. Submit it with its persisted idempotency key.
4. Record the accepted job identifier.
5. Process later status updates separately.

Avoid fire-and-forget work started with `Task.Run` inside a controller. The application can restart after returning a response, abandoning work that only existed in memory.

Be cautious with automatic HTTP retry handlers, too. Configure retries deliberately for POST requests, and preserve the original idempotency key and payload. Back off on transient failures and respect PrintScaler's rate limit of 10 requests per second, with a burst allowance of 30.

## Track print-job state separately from HTTP success

A successful API response means the submission succeeded. It does not mean someone is already holding the printed document.

PrintScaler exposes these job states:

```text
new
sent_to_client
queued
in_progress
done
error
cancelled
expired
```

Model submission and delivery separately in your own database. Useful application fields include the business document ID, printer ID, idempotency key, PrintScaler job identifier, latest state, and timestamps.

PrintScaler provides full job history and `printjob.state` webhooks. Webhooks are HMAC-SHA256 signed and retried, so validate signatures and make your webhook handler safe to process more than once. Record the event durably before scheduling expensive follow-up work.

A reported completion state should not be presented as independent proof of physical output beyond what the printing system can report. For critical workflows, include an operator confirmation or barcode scan when the business process requires that assurance.

### Plan for offline destinations

Jobs queue while the destination computer is offline and can print when it reconnects. They expire after a configurable time.

Choose that expiration to match the document's usefulness. A packing slip might remain useful when a warehouse workstation reconnects later. A time-sensitive dispatch instruction might be dangerous to print after the team has already handled the exception manually.

Your application should therefore distinguish between retrying an uncertain submission and intentionally creating a replacement after an expired or cancelled operation.

## Fix common PDF printing problems

### The request succeeds, but nothing prints

Check the dashboard's job history, the destination computer's connection, and the selected printer. Try the dashboard test page before changing C# code.

If the job is waiting for an offline computer, repeatedly submitting more requests will not restore the connection—and can create unnecessary duplicate work.

### The PDF cannot be retrieved

Verify the exact URL from outside your application's authenticated browser session. Check access permissions, document availability, and whether a signed URL has expired.

Set signed-URL validity to accommodate retrieval delays in your workflow. If that is difficult to manage reliably, consider submitting the PDF bytes as `pdf_base64` instead.

### Output is scaled, clipped, or sent to the wrong tray

Match the PDF page dimensions to the intended paper. Then inspect the printer capabilities exposed through the API before choosing paper, tray, DPI, or other settings.

PrintScaler supports PDF options including copies, duplex, paper size, tray, collate, color, fit to page, page ranges, rotation, and n-up. Do not assume every printer supports every value, or copy an option value from a different device without checking.

### It works locally but fails after deployment

Check the deployed API key, outbound HTTPS access, document URL permissions, and application logs. Your Linux application server does not need a local printer driver for the agent-based path; the destination computer needs the working printer setup.

If your processing runs in a serverless function rather than an ASP.NET Core host, the related [AWS Lambda printing guide](https://printscaler.com/blog/print-from-aws-lambda-local-printer) covers that deployment model.

## Start with one document and one destination

Before connecting every branch, test one PDF, one agent computer, and one printer. Verify normal printing, a network timeout, an offline destination, and a deliberate reprint with a new operation key.

PrintScaler charges per print request rather than per page. The Free plan includes 100 prints per month and one connected computer; review the [current plans](https://printscaler.com/pricing) when estimating production volume and location count.

ASP.NET Core printing becomes easier to operate when document generation, delivery, and physical printing are separate concerns. [Create a free PrintScaler account](https://printscaler.com/signup), connect a printer, and send your first PDF from the C# client before adding background processing and status tracking.

## Frequently asked questions

### Can ASP.NET Core print directly to a user's local printer?

A hosted ASP.NET Core application cannot automatically access a printer attached to a user's computer. You need a delivery mechanism, such as a local agent connected to a printing API, or a user-driven browser printing workflow.

### Can I print PDFs from an ASP.NET Core application running on Linux?

Yes. Your Linux-hosted application can submit PDFs to PrintScaler over HTTPS, while an agent on the destination computer handles local delivery. Your application server does not need the destination printer's driver for this architecture.

### Do I need System.Drawing.Printing to use PrintScaler from C#?

No. You can use HttpClient to submit a PDF URL or Base64-encoded PDF to the REST API. Document generation remains a separate step, so you can retain your existing PDF library.

### How do I prevent duplicate prints when an HTTP request times out?

Persist an idempotency key for each intended print operation and send it in the X-Idempotency-Key header. Retry the same request with the same key; use a new key only for an intentional new print operation.

### What happens if the printer's computer is offline?

PrintScaler queues jobs while the destination computer is offline and prints them when it reconnects, provided they have not expired. Set the configurable expiration to reflect how long the document remains useful.

### Should I send a PDF URL or Base64 content?

Use pdf_uri when you have a document URL that PrintScaler can retrieve for the required period. Use pdf_base64 when you already have the PDF bytes and want to submit them directly, accounting for the larger encoded request payload.
