.NET printing on server side from asp.net - c#

I have a Windows server running an ASP.NET application and a local printer connected to this machine. I need to print some documents from the server-side code.
So far I know, there's no managed API in .NET that is supported on server-side (service).
System.Printing namespace - is part of the WPF and is not supported to run on server-side as it may produce run-time exceptions (checked on msdn)
System.Drawing.Printing - is part of the WinForms and also not supported to run on server-side (checked on msdn)
The same problem was elaborated with help of Microsoft back in 2009 and the solution was to use an unmanaged XPS Print API as the only supported way back in that time. Problem described and solution with example posted is here: How to Print a Document on a Server via the XpsPrint API
However nowadays this is a problem as the XPS Print API is marked as not supported and may be unavailable in the future (msdn).
So, what is the supported way of printing from the server-side code?
It looks like there are more Win32 APIs that could be probably used, but there's no info on the web and it would probably be a nightmare...
Commercial solutions are accepted. Thank you.

So the best way would be to set up your printer on a print server and then install the print drivers on your web server with a reference to the printer you want to print to.
Then in code you can use System.Drawing.Printing to send the print job to whatever printer you just installed.
PrintDocument p1 = new PrintDocument();
p1.PrinterSettings.PrinterName = "\\PrintServer\NewPrinter";
p.PrintPage += new PrintPageEventHandler(this.Page_Print);
p1.Print();
protected void Page_Print(object sender, PrintPageEventArgs ev)
{
Brush b = new SolidBrush(Color.Black);
Font printFont = new Font("Lucida Sans Typewriter", 10);
ev.Graphics.DrawString("Hello World", printFont, b, x, y, StringFormat.GenericDefault);
}
Where this code will work technechally I would not recommend doing it this way since Microsoft itself says that this isn't supported.
I personally would push all print jobs to the client side. Install the printer locally like you did and then redirect the user to a page that they can print easily.
You can then write JavaScript if desired to call the browser specific print for them (if you want to automate that as well)
Or save a PDF server side and push the file as a download to the user making them download/save and then print the document via Adobe Reader or alternative app.

Related

How To: Open a Word document from .NET Core web application, edit, and save back to server?

I am looking for modern examples on how to create a SharePoint-like integration with Microsoft Word but in an ASP/C# (.NET Core) web application. In other words, my goal is to click on a Word document from my webpage (file stored on my on-premise server), open in Word (desktop application), makes changes, and save back to the server. When I say SharePoint-like integration, I mean, opening and saving are handled automatically without the user having to be bothered by saving the file locally and manually uploading it back to the server.
I have found others asking the same question but with no concrete response and most were nearly a decade old. Here are some of the articles I found but of no help:
C# and Office integration
How can I open, edit and save a word document with asp.net
https://social.msdn.microsoft.com/Forums/office/en-US/e1928f0b-6922-4f23-a1f9-09835e39f7da/how-to-opensave-word-documents-fromto-in-aspnet-webapi-using-ms-word-application
It looks like the Office Add-in platform (https://learn.microsoft.com/en-us/office/dev/add-ins/overview/office-add-ins#components-of-an-office-add-in) may be of use, but I don't necessarily want a Word add-in added to the ribbon. So not sure this is really what I want.
WebDAV may be what I need as discussed here (https://www.webdavsystem.com/ajaxfilebrowser/programming/opening-docs/open_save_docs_directly_to_server/) but not sure... and the article talks about Office 2007 so seems kind of dated.
Any help in guiding me to example, article, or forum that discusses current approaches to tackle this would greatly be appreciated.
A little more information: clients would be using Edge or Chrome browsers, on Windows 10 boxes, with Office 2016 (or later).
This question is very broad. There looks to be several parts.
A plugin that can be configured to communicate with a webservice
An Api that consumes data from the plugin
The plugin should be able to save automatically
Here is some related documentation though some are specific to Excel, though may be able to be repurposed for Word
Also note that Office plugin development is JavaScript and/or TypeScript.
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/custom-functions-web-reqs
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/custom-functions-batching
https://learn.microsoft.com/en-us/javascript/api/word/word.document?view=word-js-preview#save__
There is not any code here so even writing a specific function would be making several assumptions, but from the documentation (with a couple add ins):
// Run a batch operation against the Word object model.
Word.run(function (context) {
// Create a proxy object for the document.
var thisDocument = context.document;
// Queue a command to load the document save state (on the saved property).
context.load(thisDocument, 'saved');
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
if (thisDocument.saved === false) {
// Queue a command to save this document.
thisDocument.save();
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
//
// CODE to send your data to your server(s) via
// API (post request, or something similar)
//
console.log('Saved the document');
});
} else {
console.log('The document has not changed since the last save.');
}
});
})
.catch(function (error) {
console.log("Error: " + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});

Is it possible to print documents from a .net core 3.1 Windows Service?

tl;dr How can you print pdfs from a .net Core 3.1 Windows Service?
I've created a simple print spooler BackgroundService class, which is being run as a Windows Service, and monitors a print queue via a web api, all very happily.
The small problem I've discovered as started to write the actual printing code is that it seems .net core doesn't want people to print documents from BackgroundService classes.
The docs for System.Printing seem to suggest this anyway.
Classes within the System.Printing namespace are not supported for use
within a Windows service or ASP.NET application or service. Attempting
to use these classes from within one of these application types may
produce unexpected problems, such as diminished service performance
and run-time exceptions.
System.Drawing.Printing has a similar note in its docs, stating that it will not work reliably for Windows Services either.
Is printing from a BackgroundService Windows Service a bad thing (tm)? Is there an obvious alternative to System.Printing / System.Drawing.Printing, that my (brief) googling has failed to find? The printing requirements should be pretty simple, I've got pdf byte array data, that I just need to get to a printer somehow).
I realise I could do something like convert the spooler to a Console app, and run it from a Scheduled Task, but the Windows Service model seemed like it'd be simpler to just install and forget (it's destined for a PC next to a printer in a warehouse)
Any helpful suggestions would be much appreciated
Incredibly, we did manage to achieve the impossible - printing PDFs from a .net Core 3.1 Windows Service.
We use the FreeSpire.PDF v5.4.0 nuget package and the following code to print pre-generated pdf data, to a Zebra Label printer.
bool printedOK = true;
string printErrorMessage = "";
try
{
PdfDocument pdf = new PdfDocument(printJobResult.printJob.PrintData);
pdf.PrintSettings.PrinterName = jobInfo.PrinterAddress;
pdf.PrintSettings.DocumentName = jobInfo.Type == PrintJobType.Label ? $"Label {jobInfo.OrderNumber}" : $"DeliveryNote {jobInfo.OrderNumber}";
if(jobInfo.Type == PrintJobType.Label)
{
pdf.PrintSettings.PaperSize = new System.Drawing.Printing.PaperSize("Custom", _labelWidth, _labelHeight);
pdf.PrintSettings.SetPaperMargins(2, 2, 2, 2);
}
pdf.PrintSettings.SelectSinglePageLayout(Spire.Pdf.Print.PdfSinglePageScalingMode.FitSize, true);
_logger.LogDebug($"Paper Size - Width:{pdf.PrintSettings.PaperSize.Width} Height:{pdf.PrintSettings.PaperSize.Height} Name:{pdf.PrintSettings.PaperSize.PaperName} Kind:{pdf.PrintSettings.PaperSize.Kind} RawKind:{pdf.PrintSettings.PaperSize.RawKind}");
pdf.Print();
}
catch (Exception ex)
{
printErrorMessage = "Printing Error: " + ex.ToString();
printedOK = false;
}
Note to self - Do go and check the details of these following points...
Newer versions of the FreeSpire.PDF plugin don't allow printing, and I believe there are limits even with the 5.4.0 version (10 pages of printing I think), but for our purposes, the 5.4.0 version of the plugin has allowed us to create a tidy little delivery label print spooler, running as a Windows Service on a warehouse PC.
I did a small test for my own puposes and i did find that this works for me. My test worked for .txt and .pdf. The .png did only print some error code and unreadable text.
public Task StartAsync(CancellationToken cancellationToken)
{
FileInfo fileInfo = new FileInfo(#"c:\temp\my_pdf.pdf");
fileInfo.CopyTo(PrinterName);
return Task.CompletedTask;
}

Print to Network (Shared) Printer - Word Interop

I need to print a word document on server side to a network printer. My web page sends the document and file location to the server to open and replace mail merge items and then print the document to a preferred (not default) network printer. Preferred printer name changes when document selected on web page changes
I'm using Word 14.0 object library, Asp.Net MVC 4.0, .Net Framework 4.0, IIS 7 on windows server 2008 R2. On IIS I created an app pool that runs on an specific account Identity (accountName#DomainName). Load user profile set to true to load the network printer connections in to the registry. I allowed the Account to have permissions to run the word COM interop services. I was successful to open the document and replace the mail merge fields and save it as pdf to send the file as attachment to an email.
The word application has the default printer in its ActivePrinter property, so that I could print to default printer as well. But my final goal is to print the word to a preferred network printer before I close the word application and active document.
The following two Methods were causing exceptions if I try to change the ActivePrinter property.
Word.Application wordApp = new Word.Application();
First Method:
wordApp.ActivePrinter = "preferredPrinterName";
Second Method;
object[] oWordDialogParams = { "\\<serverName>\<PrinterName>", true };
string[] argNames = { "Printer", "DoNotSetAsSysDefault" };
object wordBasic = wordApp.WordBasic;
wordBasic.GetType().InvokeMember("FilePrintSetup"
, System.Reflection.BindingFlags.InvokeMethod
, null
, wordBasic
, oWordDialogParams
, null
, null
, argNames);
I found that the Word application object is not loading all the printers installed to the user account. It only loads the default printer. I'm assuming that it was the reason for exceptions when above two methods were attempting to change or add preferred printer to the application object because the printer I'm trying to set was never found in the active printers list.
How do I get all installed shared printers under user profile loaded in to the Word application object?
Using Office interop in a server-scenario (like ASP.NET, Windows Service etc.) is NOT supported by MS - see http://support.microsoft.com/default.aspx?scid=kb;EN-US;q257757#kb2
Additionally there have been several security-related changed since Windows Vista which basically make it really hard to do anything "desktop-like" in a Windows Service (IIS/ASP.NET is just a special case of Windows Service in this regard).
Another point is "printing" from a server-scenario is likely to cause problems since IIS is a (special) Windows Service... Windows Service usually don't have a "full/real" desktop which in turn is needed for printing robustly...
I don't think that there an easy solution for your scenario...
I would break it down to different components:
Word document handling (for example with Aspose.Words)
Create a PDF from the resulting Word file (for example with Aspose.Words)
Implement a HotFolder on the target network printer
Copy the PDF over to that HotFolder for printing
This would be a robust and supported option for your scenario...

How to write and send a command to Brother QL serie label printer?

I'm now trying to write a simple program in C# that sends command to the printer to print a plain text but don't know how to. There are 2 main problems that I'm facing now:
1. How to communicate with the printer?
After doing some google search but not getting a satisfying result I went to Brothers' main page and found there a so-called b-PAC3 SDK.
The b-PAC* Software Development Kit is a software tool for Microsoft® Windows® that allows customized labels to be printed from within your own applications.
After having downloaded and installed it, in the directory where it's installed, I found a folder named "Samples"- there are sample codes written in some different language (VB, VS, VSC, ...) I hoped that these sample codes would work since this SDK and the printer come from the same company. But they didn't. Let me show you one of these samples here: (code in C#)
/*************************************************************************
b-PAC 3.0 Component Sample (RfidRW)
(C)Copyright Brother Industries, Ltd. 2009
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleSampleCSharp
{
class Program
{
private const int NOERROR = 0;
private const string ANTENNA_READER_WRITER = "Reader/Writer side";
static void Main(string[] args)
{
// Create Rfid Instance
bpac.RfidClass rfid = new bpac.RfidClass(); // Rfid Instance
string selectedDevice; // selected device
/* GetInstalledDevices */
Console.WriteLine("==GetInstalledDevices()==");
object[] arrDevices = (object[])rfid.GetInstalledDevices();
if (rfid.ErrorCode == NOERROR)
{
Console.WriteLine("Succeed to GetInstalledDevices()");
int index = 0;
foreach (string device in arrDevices)
{
Console.WriteLine(String.Format("[{0}] {1}", index, device));
index++;
}
// select device
Console.WriteLine("Please Select Device");
int selectedDeviceIndex = int.Parse(Console.ReadLine());
selectedDevice = arrDevices[selectedDeviceIndex].ToString();
}
else
{
Console.WriteLine("Failed to GetInstalledDevices()");
goto CleanUp;
}
// ....
}
}
}
When I run this code, the first problem comes out: (it displayed exactly as in quote bellow, sorry, I can't post image due to low reputation):
==GetInstalledDevices()==
Succeed to GetInstalledDevices()
Please Select Device
There wasn't any error but seems like program can't find my device, I don't have any idea why this happens.
2. How to write a QL-style command?
I know that each kind of printer has its own command language so after searching on Brother's site I found a reference:
Brother QL Series
Command Reference
(QL-500/550/560/570/580N/
650TD/700/1050/1060N)
I myself have no experience in working with thermal printer and unfortunately there isn't any sample in this command reference which makes it really difficult for me to figure out how the command should be written.
Has anyone worked with Brother QL serie printers before?
P.S: The printer that I'm using is Brother QL 560.
To communicate with the printer, you need a few things:
Get a USB library, like libusb (http://libusb.info/)
Install a driver that will allow you to access the printer via libusb, like Zadig for example (http://zadig.akeo.ie/)
Download the printer's Command Reference from the Internet ("Brother QL Series Command Reference")
Using the information provided in chapter 7 of the command reference and the samples that come with libusb, make a small routine that will detect and open a communication channel with the printer via USB.
Then, using the rest of the information available in the manual, send a series of ESC commands to the printer to either configure it or print labels.
PS: If you need to improve your background on USB communication, I recommend an excellent reference called "USB in a Nutshell", available at beyondlogic dot org (I can't post more than two links).
I think OPOS (from Microsoft) should be the one of the solutions for your case, provided with Brother QL 560 offering its own opos driver. Once you get the driver (in dll), you can just start developing as easily as using general web controls.

Accessing the Ubuntu terminal from Mono

Background
I'm writing an web application so I can control an Ubuntu Server from a web site.
One idea I had was to run the 'screen' application from mono and redirect my input and output from there.
Running 'screen' from mono:
ProcessStartInfo info = new ProcessStartInfo("screen", "-m");
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardInput = true;
var p = new Process();
p.StartInfo = info;
p.Start();
var output = p.StandardOutput;
var input = p.StandardInput;
but running 'screen' with the RedirectStandardInput gives out the error:
Must be connected to a terminal
I've tried many different arguments and none seems to work with 'Redirecting Standard Input'
Other ideas for controlling a server will be greatly appreciated
I think this is the typical question in which you're asking how to implement your solution to a problem, instead of asking how to solve your problem. I don't think you should do hacky things like making a web app that tunnels the user actions to the server via a terminal.
I think you can bypass all that and, without writing a single line of code, take advantage of what the platform (Gtk+ in this case) already provides you:
You could run gnome-terminal in the server with the Broadway GDK backend. This way the gnome-terminal app will not run in the server, but open a web server on the port you specify. Later, you can use any WebSockets-enabled browser to control it.
This is the easiest and less hacky solution compared to the other ones offered so far. If you still are excited about using Mono for web development you still can, and you could embed this access in an iFrame or something.
(PS: If you don't want to depend on GTK being installed in the server; you could just use WebSockets in your client part of the webpage to be able to send events from the server to the client, and the library SSHNET to send the user's input directly through the wire.)
screen will need a terminal of some sort. It's also gigantically overkill.
You may wish to investigate the pty program from the Advanced Programming in the Unix Environment book (pty/ in the sources) to provide a pseudo-terminal that you can drive programmatically. (You'd probably run the pty program as-provided and write your driver in Mono if you're so inclined.) (The pty program will make far more sense if studied in conjunction with the book.)
The benefit to using the pty program, or functionality similar to it, is that you'd properly handle programs such as passwd that open("/dev/tty") to prompt the user for a password. If you simply redirect standard IO streams via pipe() and dup2() system calls, you won't have a controlling terminal for the programs that need one. (This is still a lot of useful programs but not enough to be a remote administration tool.)
There may be a Mono interface to the pty(7) system; if so, it may be more natural to use it than to use the C API, but the C API is what does the actual work, so it may be easier to just write directly in the native language.
A different approach to solve the same problem is shellinabox. Also interesting is this page from the anyterm website that compares different products that implement this kind of functionality.
Using shellinabox is very simple:
# ./shellinaboxd -s /:LOGIN
(this is the example given on their website) will start a webserver (on in your case the Ubuntu server). When you point your browser to http://yourserver:4200 you'll see a login screen, just like you would see when opening a session with ssh/putty/telnet/... but in your browser.
You could provide the required remote access functionality to the server's shell by just including an iframe that points to that service in your application's webpage.

Categories