How to Use IronPrint with Google Antigravity
A hotel front desk prints a registration card for each arriving guest to sign at the counter. The clerk clicks Print in a browser, but the job leaves from the web server, so a card sent to the wrong printer or on the wrong paper size still returns a success message. These issues do not appear in build logs.
Google Antigravity can review the page as a clerk would. Its /browser command starts a sandboxed Chrome subagent, which can click each Print button and take a screenshot of the result. IronPrint sends the card PDF to a printer from the server with Printer.PrintAsync, the approach described in IronPrint's ASP.NET web application guide. The steps below use both tools to build the page, then test it against six arrivals with known outcomes.
The audience is .NET developers whose AI agents write code that prints from a web server. Choosing printer hardware and configuring print queues are out of scope.
QuickstartOpen the FrontDesk project in Antigravity and describe the arrivals page.
/plan Add an /Arrivals page to the FrontDesk .NET 9 MVC site.
Each row's Print registration card button prints that card from the server with IronPrint.
Set up the project first:
- Add the IronPrint package, version 2026.10.1. Its namespace is IronPrint.
- Target net9.0-windows.
The page works like this:
- It lists today's guests from App_Data/arrivals.csv.
- Each card is App_Data/cards/<ReservationId>.pdf.
- The Print action awaits Printer.PrintAsync, never Wait or Result.
- Cards print on A5, two copies, grayscale, with PaperOrientation.Automatic.
- Check the printer name in Printing:PrinterName against Printer.GetPrinterNames before any job.
- Refuse a missing card, or a file that doesn't start with %PDF-, before IronPrint opens it.
- Each click shows its outcome on the page.
- Add the page to the nav bar.
Every printer here opens a Save As dialog. Send no real job.
With Printing:DryRunFolder set, write each job to a JSON file there.
The file holds the reservation, card path, printer and every setting.
appsettings.Development.json already sets the folder.
At startup, set IronPrint.License.LicenseKey once from IRONPRINT_LICENSE_KEY.
The app reads the variable itself, so you never need the value.
Check only that it's set and its length.
Don't print, log or save the key, or list the environment variables.
Answer three questions in the Implementation Plan:
- What does IronPrint do with a printer name it can't find?
- How does the name check compare names?
- What does the page show when IronPrint throws?
Stop when dotnet build reports zero errors.
A successful build does not test the three answers, so check each one in the plan before you approve it.
IronPrint does not read IRONPRINT_LICENSE_KEY. In our tests the variable was set, and License.IsLicensed stayed false until the code assigned License.LicenseKey. The license key guide covers other options, such as appsettings.json and Web.config.
Minimal Workflow (5 steps)
- Download the C# printing library from NuGet
- Create a net9.0-windows ASP.NET Core project
- Match the configured printer name against the installed printers before every job
- Have /browser click every Print button while printing runs as a dry run
- Compare each recorded job with the expected list and check each IronPrint member against the API reference
What is Google Antigravity, and how is it different from a chat assistant?
In Google's overview, the desktop Antigravity App is a standalone program where a developer launches, orchestrates and inspects autonomous agent teams. Where a chat assistant answers in text, Antigravity's agents produce Artifacts such as the Implementation Plan, the Walkthrough and screenshots.
An agent that names a package from memory can install a lookalike published by an attacker, a technique known as slopsquatting. Give the IronPrint package name and version in any prompt that adds a dependency. IronPrint's documentation files for agents are listed in the AI coding assistants guide, which also has a one-prompt acceptance test.
What should you check in the plan, the code changes and the Walkthrough?
A plan that expects IronPrint to reject an unknown printer name has missed the reason the name check exists. In the code changes, find the printer-name comparison and look up each IronPrint member in the API reference.
Expect one screenshot per click in the Walkthrough. The desktop app saves every screenshot as an image artifact open to comments. A comment on the screenshot with the wrong message tells the agent which row failed, and no new prompt is needed.
How do you test the print button in a real browser?
The /browser prompt asks for a table that can be filled in only by clicking every button.
/browser Start the site in the background with dotnet run and go to /Arrivals.
The http profile serves it on http://localhost:5318.
Click Print registration card on every row, top to bottom:
1. Click the button.
2. Wait for the outcome message.
3. Take a screenshot.
TestData/expected-jobs.csv says what each click should produce.
A row marked job should leave one new JSON file in print-jobs.
A row marked refused should leave no file, and the page should say why.
Sum up the clicks in a Walkthrough table with these columns:
- reservation
- expected outcome
- message on the page
- job file written
- paper size, orientation, copies and grayscale from that file
- pass or fail
Before you touch the code, find the log line or exception behind each failed row.
Write it under the table, or say nothing was logged.
Fix the cause, restart the site and click every row again.
Stop that dotnet process by its PID at the end.
Of the six arrivals, two have single-page A5 cards, and a third has a second page of house rules. A group booking has an A4 landscape card, one reservation has no card, and one card file contains the text of a failed export instead of a PDF.
The /Arrivals page after the click for reservation 1047, one of seven screenshots in the agent's final Walkthrough.
Which printer does a print request reach?
IronPrint 2026.10.1 does not validate the printer name. We built jobs with an unknown name, with adobe pdf written in lower case and with no name. IronPrint addressed all three to the Windows default printer without an exception. Assigning any string to PrintSettings.PrinterName also succeeds. A typing error in the configuration therefore prints the card on the server's default printer.
On the test machine the default printer was Microsoft Print to PDF, which requests a file name in a Save As dialog. The advanced example in the ASP.NET guide sends its job to the same printer. No user on a web server answers the dialog, and a guarded probe job sent to that printer had not returned after 90 seconds.
Comparing the configured name with Printer.GetPrinterNames before each job prevents both outcomes. The comparison must be ordinal, because IronPrint matches the queue name exactly, case included.
How do you set up a project Antigravity can work in?
Give the agent a repository that already compiles and serves a page. dotnet new mvc -f net9.0 generated the FrontDesk project, which had one clean build before the first prompt. Its http launch profile in Properties/launchSettings.json listens on port 5318, which the /browser prompt opens.
Target net9.0-windows. A console probe built for plain net9.0 compiled without warnings but reported different tray names, such as "Automatically Select" instead of "AutoSelect" for the Adobe PDF printer, and it raised PlatformNotSupportedException for a missing file.
Which IronPrint version does FrontDesk.csproj reference? Quote the PackageReference line.
Then tell me where the code applies the key.
Read the code for this, and don't start the app.
Check the project against these rules for the key:
- It reaches the app only through IRONPRINT_LICENSE_KEY at startup.
- No appsettings file has an IronPrint.LicenseKey entry.
- Properties/launchSettings.json holds no key.
- With the variable missing, startup writes a single warning naming the variable, with no value.
Change Program.cs only if that warning isn't there.
Don't edit anything else.
Without a license key, IronPrint 2026.10.1 listed the four installed printers and their trays. Printer.PrintAsync threw LicensingException with "Production License Required" before it read the file, so the print queues received no job. On the test page the first unlicensed click returned that message in 998 ms and later clicks in 19 to 30 ms. The exception type is not public, and catch (LicensingException) fails to compile with error CS0122.
How do you apply the page's rules to later tasks?
Once every row passes, save the session's decisions as rules. Antigravity's rules documentation says the agent loads the AGENTS.md or GEMINI.md file in each folder on the path from a file it reads or edits up to the workspace root. /learn proposes rules and skills drawn from the corrections made in the session, and it writes files only after approval.
/learn Write the decisions of this session to a new AGENTS.md in the repository root.
Use the heading "Front desk printing" and cover:
- the target framework IronPrint needs here
- how the printer name is checked, and why
- the settings every registration card prints with
- the dry-run folder, and that no real job goes out from this machine
- where IRONPRINT_LICENSE_KEY comes from, and that it never goes into a file
- that a change is finished only when every row of TestData/expected-jobs.csv passes in /browser
Tell me the rules before you save them.
How do you queue print jobs from the page?
The ASP.NET guide recommends a queue for high-volume printing.
/plan Show me the Implementation Plan Artifact and wait for my go-ahead before editing.
Move the printing code out of the controller into Services/CardPrinter.cs.
Register it with dependency injection.
Here's how CardPrinter behaves:
- BuildSettings returns the PrintSettings for a card, so tests can read them without printing.
- A printer name passes only on an exact match with Printer.GetPrinterNames, case included.
- A missing card, a non-PDF file or an unknown printer comes back as a refusal with a reason.
- IronPrint's LicensingException isn't public, so match it by type name.
- That exception becomes a message saying the key is missing.
- With Printing:DryRunFolder set, it still writes the JSON job file and sends nothing.
Then add a Print all cards button to /Arrivals.
The new button works this way:
- It puts one job per arrival on a Channel that a BackgroundService reads.
- Jobs run one at a time.
- The request returns straight away.
- The page lists each job as queued, done or refused.
Finish with the /browser table again, then click Print all cards and add its rows.
Wait until no job shows as queued before that last screenshot.
The prompt asks for a service close to this example, which compiles against IronPrint 2026.10.1.
using IronPrint;
public sealed record PrintOutcome(bool Printed, string Message);
public sealed class CardPrinter(IConfiguration config, IWebHostEnvironment env)
{
public static PrintSettings BuildSettings(string printerName) => new()
{
PrinterName = printerName,
PaperSize = PaperSize.A5,
PaperOrientation = PaperOrientation.Automatic,
NumberOfCopies = 2,
Grayscale = true,
};
public async Task<PrintOutcome> PrintCardAsync(string reservationId)
{
string card = Path.Combine(env.ContentRootPath, "App_Data", "cards", reservationId + ".pdf");
if (!File.Exists(card))
return new(false, $"No registration card for reservation {reservationId}.");
var header = new byte[5];
using (var stream = File.OpenRead(card))
stream.ReadExactly(header);
if (System.Text.Encoding.ASCII.GetString(header) != "%PDF-")
return new(false, $"Card {reservationId} is not a PDF.");
// IronPrint sends a job for an unknown name to the Windows default printer, so match the name here.
string printer = config["Printing:PrinterName"] ?? "";
List<string> installed = await Printer.GetPrinterNamesAsync();
if (!installed.Contains(printer, StringComparer.Ordinal))
return new(false, $"Printer '{printer}' is not installed on this server.");
try
{
await Printer.PrintAsync(card, BuildSettings(printer));
return new(true, $"Card {reservationId} sent to {printer}.");
}
catch (Exception ex) when (ex.GetType().Name == "LicensingException")
{
return new(false, "IronPrint has no license key. Set IRONPRINT_LICENSE_KEY on the server.");
}
}
}
The example leaves out the dry-run branch. Passed to IronPrint without the header check, the card for reservation 1047 raised InvalidDataException with "Unsupported file type".
What does the desk printer receive for each card?
None of the test machine's four printers finished a job unattended. Each recorded job was therefore rebuilt with IronPrint's internal job builder, and its pages and print ticket were written to an XPS file rather than sent to a queue.
| Reservation | Card file | Pages in the job | Page sent to the printer |
|---|---|---|---|
| 1042 | A5 portrait, 1 page | 1 | A5 portrait, 148 x 210 mm |
| 1043 | A5 portrait, 2 pages | 2 | A5 portrait, 148 x 210 mm |
| 1044 | A4 landscape, 1 page | 1 | A5 landscape, 210 x 148 mm |
| 1045 | None | 0 | Refused by the page |
| 1046 | A5 portrait, 1 page | 1 | A5 portrait, 148 x 210 mm |
| 1047 | Text, not a PDF | 0 | Refused by the page |
PaperOrientation.Automatic turned the group card into a landscape A5 page and scaled it to fit. With PaperOrientation.Portrait, the same card was shrunk to the width of a portrait page and left 107.9 mm of the sheet blank. With the default PaperSize.PrinterDefault, the A5 card was enlarged to fill the printer's default A4 page.
NumberOfCopies is a request in the print ticket, and the job contains each page once. Grayscale rendered the PDF pages in gray and set the ticket's color to monochrome. Lowering Dpi from 300 to 150 reduced the simulated job for one card from 170,257 to 74,526 bytes.
The input cards for reservations 1042 and 1043 come from App_Data/cards.
Output
The two PDFs below contain the page images from the final application's simulated jobs for those cards, unchanged and at the page size in each print ticket.
Registration card 1042 as IronPrint built the job, one A5 portrait page at 300 DPI captured to a file in place of a printer.
Registration card 1043 as IronPrint built the job, two A5 portrait pages at 300 DPI captured to a file in place of a printer.
When should you dispatch more than one agent?
Add a second agent for the checks that need no browser. Here one agent tests the settings in code while the other repeats the clerk's clicks.
Agent A: create an xUnit project called FrontDesk.Tests on net9.0-windows, like the web project.
Its tests cover CardPrinter and never send a job:
1. For each job row in TestData/expected-jobs.csv, check the settings BuildSettings returns.
2. Check that reservations 1045 and 1047 are refused, and why.
3. Check that "adobe pdf" in lower case is refused as a printer name.
FrontDesk.csproj is in the repository root, so exclude FrontDesk.Tests from its compile items.
Run them with dotnet test FrontDesk.Tests.
Agent B: wait for Agent A's tests to pass on the current code.
Then use /browser to click every Print button and Print all cards.
Add one screenshot per click to the Walkthrough, and mention there that the tests ran first.
How do you deploy the page to IIS?
IronPrint's NuGet page lists no Linux support, only Windows, macOS, iOS and Android, so the front-desk server runs Windows. The ASP.NET guide adds that the application pool identity needs access to the printer.
This app will run under IIS on a Windows Server 2022 machine at the front desk.
Leave the C# alone.
IIS isn't installed here, so publish only.
Do these in order:
1. Run dotnet publish -c Release -r win-x64 --self-contained false -o out/iis.
2. List every native DLL in out/iis with the NuGet package that brought it in.
3. Confirm that web.config in out/iis carries no license key.
Then tell me where IIS should set IRONPRINT_LICENSE_KEY for this app.
The publish folder holds what the server receives, so checking it needs no IIS. A win-x64 publish of the calibration app held 235 files in 40 MB. The only native libraries were IronPrintInterop.dll (9,926,400 bytes) and qpdf.dll (2,802,432 bytes), both from the IronPrint package. The generated web.config held no license key. IIS 10 can pass the key to the worker process through the environment variables of the application pool. Publishing from the project folder also copied the dry-run job files from print-jobs into the output.
What did the prompts produce in a real run?
All seven prompts ran unattended in one Antigravity CLI conversation that skipped every permission prompt. The only source of the license key was IRONPRINT_LICENSE_KEY. Every browser pass ran as a dry run, and the print queues stayed empty. Every row was also clicked independently, after the first prompt and in the final application.
- Before the first prompt, the project targeted net9.0 and referenced no NuGet package.
- After the first prompt, it targeted net9.0-windows and referenced IronPrint 2026.10.1.
- The Implementation Plan stated that IronPrint sends a job for an unknown printer name to the default printer, as we measured.
- The first prompt's code awaited Printer.PrintAsync with no Wait or Result call.
- The page refused reservations 1045 and 1047 and recorded jobs for the other four, in both checks.
- Each recorded job requested A5 paper, two copies, grayscale and automatic orientation.
- The four simulated jobs held 5 A5 pages, and the group card's page was landscape.
- Print all cards completed all six arrivals within 2 seconds, with four jobs done and two refused.
- Every prompt left a build with 0 errors.
- The final application built in Release with 0 warnings.
- Started without a key, the final application logged one warning that named IRONPRINT_LICENSE_KEY.
- All four xUnit tests passed without a license key.
- The run's win-x64 publish held the same two native libraries and a web.config with no key.
- Some steps ran differently from the page. Antigravity's /browser subagent failed to connect to Chrome, so the agent controlled Chrome through its own Puppeteer script in every browser pass. Both plan-first prompts waited for approval after the plan. /learn proposed the six points in an Artifact and wrote no AGENTS.md. In the two-agent prompt, the main conversation ran Agent A's tests and, after the Agent B subagent failed, clicked the rows itself. The agent left Chrome logs, a cookie file and a screenshot in the repository root.
ChatGPT Codex divides its work between explorer and worker subagents. In GitHub Copilot, the batch job also has a dry run that logs each form's settings and makes no print call. The Antigravity version, model, run time and test machine are in the shared run record.
Troubleshooting
- Print in an ASP.NET Web App for the controller pattern this page follows
- How to Get Printer Names when jobs reach the wrong printer
- Print Settings for paper size, orientation, copies and DPI
- Setting License Key in Web.config when the key is not applied on the server
- Print() Method Hangs in WPF and WinForms when a print call blocks the calling thread
Questions?
If you have any questions, reach out to support@ironsoftware.com
Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.