Klicke hier für den deutschen Artikel.
Initial situation
Printing must be performed on the server side if the printer is set up directly on the server or within the server network and the client does not have direct access to that printer. In this context, it is generally important to distinguish whether printing should be done on the client side (locally in the user’s browser) or on the server side.
Example: Upon receipt of an order, the ASP.NET application automatically prints a delivery slip on the network printer in the warehouse, regardless of which user in the office initiates the order in the browser.
What should you keep in mind?
A .NET web application running in IIS is typically executed in a worker process and, by default, runs under its own application pool identity, for example IIS APPPOOL\<MyAppName>.
To ensure that the desired printer on the server can be used for printing, it must be visible and accessible to the executing identity. IIS runs each worker process under the configured application pool identity. In practice, this means that the following prerequisites must be met and taken into account:
- Install printers on the server system-wide whenever possible
- Grant the necessary print permissions to the application pool identity for network printers
- User-specific printer connections for a logged-in user are often not visible to IIS
- If necessary, run the application pool under a dedicated service account and configure the printers for that account
- There is no reliable way to identify only physical printers. Therefore, virtual printers, for example, must be filtered out based on their driver, name, or port. These can trigger a “Save As” dialog and thereby block the worker process. In a controlled server environment, an explicit blacklist of disallowed printer names can therefore be helpful
The available printers are thus determined on the server side. This returns the printers on the Windows server that are visible to the application pool’s identity—not the user’s client-side printers.
In this process, GetPhysicalPrinters (see below) initially returns all printers detected via Win32_Printer, including virtual devices such as Microsoft Print to PDF. Win32_Printer is suitable for determining relevant printer properties. Among other things, this Windows class provides the name, driver, port, and information on whether a printer is connected locally or as a network printer.
Identifying available physical printers
...
public sealed class ServerPrinterModel
{
public string Name { get; init; } = "";
public string Server { get; init; } = "";
public string DriverName { get; init; } = "";
public string PortName { get; init; } = "";
public bool IsLocal { get; init; }
public bool IsNetwork { get; init; }
public bool IsOffline { get; init; }
public bool IsVirtual { get; set; }
}
...
The main function for detecting printers looks like this:
...
public IReadOnlyList<ServerPrinterModel> GetPhysicalPrinters()
{
const string searchQuery = @"SELECT * FROM Win32_Printer";
using var searcher = new ManagementObjectSearcher(@"root\CIMV2", searchQuery);
using var results = searcher.Get();
var printers = new List<ServerPrinterModel>();
foreach (ManagementObject printer in results)
{
var item = new ServerPrinterModel
{
Name = GetString(printer, "Name"),
Server = GetString(printer, "ServerName"),
PortName = GetString(printer, "PortName"),
DriverName = GetString(printer, "DriverName"),
IsLocal = GetBool(printer, "Local"),
IsNetwork = GetBool(printer, "Network"),
IsOffline = GetBool(printer, "WorkOffline")
};
item.IsVirtual = IsVirtualPrinter(item);
printers.Add(item);
}
return printers
.OrderBy(p => p.Name)
.ToList();
}
Support functions for identifying printers:
...
private static bool IsVirtualPrinter(ServerPrinterModel printer)
{
var description = $"{printer.Name} {printer.DriverName}".ToUpperInvariant();
string[] virtualPrinterTerms =
{
"MICROSOFT PRINT TO PDF",
"MICROSOFT XPS",
"ONENOTE",
"FAX",
"PDFCREATOR",
"PDF24",
"ADOBE PDF",
"PDF-XCHANGE",
"PDF ARCHITECT",
"SEND TO ONENOTE"
};
if (virtualPrinterTerms.Any(description.Contains))
return true;
var port = printer.PortName.ToUpperInvariant();
return port == "FILE:"
|| port == "PORTPROMPT:"
|| port == "NUL"
|| port == "NUL:";
}
private static string GetString(ManagementObject printer, string propertyName)
{
return Convert.ToString(printer[propertyName]) ?? "";
}
private static bool GetBool(ManagementObject printer, string propertyName)
{
return printer[propertyName] is bool value && value;
}
...
Set a specific printer for List & Label printing
Now, to print using List & Label, you must explicitly specify the printer on which the printout is to be produced. The ListLabel-object is prepared for this as follows:
...
private enum PrinterActionMode
{
DOM,
PFile,
Export
}
...
using (ListLabel LL = new ListLabel())
{
LL.LicensingInfo = "<MyLicensingInfo>";
// define important options for server-side printing
LL.Core.LlSetOption(LlOption.ProhibitUserInteraction, 1); // web server mode -> no interaction
LL.Core.LlSetOption(LlOption.NoPrinterPathCheck, 1); // do not validate printer paths
// define general print/export options like FileRepository and DataSource
LL.FileRepository = <MyRepository>;
LL.DataSource = <MyDataSource>;
LL.AutoProjectFile = "<MyProjectToPrint>";
LL.AutoProjectType = <ProjectTypeToPrint>;
// use one of the following methods here for physical printing...
switch (printerActionMode)
{
//...
}
}
...
In general, there are the following options:
PrinterActionMode.DOM
During the printing process, the project file to be printed is loaded in the background using the DOM API. In the process, the desired printer is temporarily set for all regions:
...
// temporarily set the printer for all regions in the project
case PrinterActionMode.DOM:
{
LL.AutoDestination = LlPrintMode.Normal;
LL.Print(printerName);
}
break;
...
PrinterActionMode.PFile
A P-file is used or created that contains the desired printer in the PrinterSettings and is then used for printing:
...
// using/creating the P-File with the mentioned printer name
case PrinterActionMode.PFile:
{
LL.Core.LlSetPrinterInPrinterFile(
LL.AutoProjectType,
LL.AutoProjectFile,
printerName);
LL.Print();
}
break;
...
PrinterActionMode.Export
Printing is done in two steps: first, a preview file (*.ll) is generated, and then it is printed on the desired printer:
...
case PrinterActionMode.Export:
{
// optional: define P-File also, to be sure the designer-variable
// LL.Device.PrinterName is set to the correct printer name
// LL.Core.LlSetPrinterInPrinterFile(
// LL.AutoProjectType,
// LL.AutoProjectFile,
// printerName);
// 1. generate the preview file
var previewFileName = Path.Combine(
Path.GetTempPath(),
$"tempoutput-{Guid.NewGuid():N}.ll");
try
{
LL.Export(new ExportConfiguration(
LlExportTarget.Preview,
previewFileName,
LL.AutoProjectFile));
// 2. open and print the preview file
using (var previewFile = new PreviewFile(previewFileName, true))
{
previewFile.Print(printerName, printerName);
}
// 3. the temporary preview file can also be archived
// or processed further before it is deleted
}
finally
{
if (System.IO.File.Exists(previewFileName))
System.IO.File.Delete(previewFileName);
}
}
break;
...
Tip: If you also plan to use physical printers on other server systems, you can use user impersonation to access printers in other network environments or on other systems. This requires that the necessary login credentials be available both when identifying the printer and when using it.