I am developping an intranet ASP.NET MVC2 application for a small business. The small business has multiple type of printers and depending on what is required a print request will be sent (from the browser/user) to the server and the server will dispatch the print job to the right printer accordingly. Please note that it is a completely new environment for them and I have control over pretty much everything. I will more than likely use a very lightweight OS (maybe Asus ExpressGate or Chrome OS depending on launch date?) so the users cannot have any printers installed but the server will have everything setup.
Here is my question:
Is there a simple way to print from the server side (without dialog of course because there won't be anyone waiting to click them) a page by using the html link as a parameter and keeping the HTML format of course.
I have seen a few possibilities of COM stuff out there but if there are possibilities to avoid this by using a .net class I would appreciate. I am using .net 4.0. I will however take any suggestions even if it is COM based.
Edit: Please note that any workaround that makes sense would also be taken into consideration, a quick (non studied yet) example would be to transfer this html to a doc file and sending this file to the printer.
Edit-Code taken off for lack of use.
Edits2: Following this link: Print html document from Windows Service in C# without print dialog
Vadim's holy grail solution DOES work. However, it has limitations like using the default printer only. I am modifying the default printer before the print occurs this causes the print to go to the right printer. I can see some concurrence issues happening here but so far this is the best i came up with (most of this code is from Vadim and I give him the full credit for this):
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.Current开发者_开发知识库Thread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short PRINT_WAITFORCOMPLETION = 2;
const int OLECMDID_PRINT = 6;
const int OLECMDEXECOPT_DONTPROMPTUSER = 2;
using(var browser = new WebBrowser())
{
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, PRINT_WAITFORCOMPLETION);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//Verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
ManagementBaseObject outParams =
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
}
A couple of articles for readng which maybe helpful are:
- http://msdn.microsoft.com/en-us/library/system.drawing.printing.printersettings.aspx
- http://msdn.microsoft.com/en-us/library/system.drawing.printing.printdocument.aspx
Will update if I find anything else. Hope this helps.
This is a parallel helper for .net 4.0 only that manages collision/threads.
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("numberOfThreads");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
}) {IsBackground = true};
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
I use 2 enums, one is for the web browser control the OLECMDID:
public enum OLECMDID
{
OLECMDID_OPEN = 1,
OLECMDID_NEW = 2,
OLECMDID_SAVE = 3,
OLECMDID_SAVEAS = 4,
OLECMDID_SAVECOPYAS = 5,
OLECMDID_PRINT = 6,
OLECMDID_PRINTPREVIEW = 7,
OLECMDID_PAGESETUP = 8,
OLECMDID_SPELL = 9,
OLECMDID_PROPERTIES = 10,
OLECMDID_CUT = 11,
OLECMDID_COPY = 12,
OLECMDID_PASTE = 13,
OLECMDID_PASTESPECIAL = 14,
OLECMDID_UNDO = 15,
OLECMDID_REDO = 16,
OLECMDID_SELECTALL = 17,
OLECMDID_CLEARSELECTION = 18,
OLECMDID_ZOOM = 19,
OLECMDID_GETZOOMRANGE = 20,
OLECMDID_UPDATECOMMANDS = 21,
OLECMDID_REFRESH = 22,
OLECMDID_STOP = 23,
OLECMDID_HIDETOOLBARS = 24,
OLECMDID_SETPROGRESSMAX = 25,
OLECMDID_SETPROGRESSPOS = 26,
OLECMDID_SETPROGRESSTEXT = 27,
OLECMDID_SETTITLE = 28,
OLECMDID_SETDOWNLOADSTATE = 29,
OLECMDID_STOPDOWNLOAD = 30,
OLECMDID_FIND = 32,
OLECMDID_DELETE = 33,
OLECMDID_PRINT2 = 49,
OLECMDID_PRINTPREVIEW2 = 50,
OLECMDID_PAGEACTIONBLOCKED = 55,
OLECMDID_PAGEACTIONUIQUERY = 56,
OLECMDID_FOCUSVIEWCONTROLS = 57,
OLECMDID_FOCUSVIEWCONTROLSQUERY = 58,
OLECMDID_SHOWPAGEACTIONMENU = 59,
OLECMDID_ADDTRAVELENTRY = 60,
OLECMDID_UPDATETRAVELENTRY = 61,
OLECMDID_UPDATEBACKFORWARDSTATE = 62,
OLECMDID_OPTICAL_ZOOM = 63,
OLECMDID_OPTICAL_GETZOOMRANGE = 64,
OLECMDID_WINDOWSTATECHANGED = 65,
OLECMDID_ACTIVEXINSTALLSCOPE = 66,
OLECMDID_UPDATETRAVELENTRY_DATARECOVERY = 67
}
The other is a custom made for whatever you need to print:
public enum PrintDocumentType
{
Bill,
Label //etc...
}
Now, here is the my printer helper that I use which sets the default printer (and prints to it), also changes the margin according to what I need printed:
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice, PrintDocumentType printDocumentType)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
IeSetup(printDocumentType);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short printWaitForCompletion = 2;
const int oleCmdExecOptDontPromptUser = 2;
using(var browser = new WebBrowser())
{
WebBrowserHelper.ClearCache(); /*needed since there is a major cache flaw. The WebBrowserHelper class is available at http://www.gutgames.com/post/Clearing-the-Cache-of-a-WebBrowser-Control.aspx with some slight changes or if website is taken off, it is based heavily on http://support.microsoft.com/kb/326201*/
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
((IWebBrowser2)ie).ExecWB(SHDocVw.OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, oleCmdExecOptDontPromptUser, printWaitForCompletion);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
/// <summary>
/// Making sure the printer doesn't output the default footer and header of Internet Explorer (url, pagenumber, title, etc.).
/// </summary>
public void IeSetup(PrintDocumentType printDocumentType)
{
const string keyName = @"Software\Microsoft\Internet Explorer\PageSetup";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName, true)) {
if (key == null) return;
key.SetValue("footer", "");
key.SetValue("header", "");
switch (printDocumentType)
{
case PrintDocumentType.Label:
key.SetValue("margin_top", "0.12500");
key.SetValue("margin_bottom", "0.12500");
key.SetValue("margin_left", "0.25000");
key.SetValue("margin_right", "0.25000");
break;
case PrintDocumentType.Bill:
key.SetValue("margin_top", "0.75000");
key.SetValue("margin_bottom", "0.75000");
key.SetValue("margin_left", "0.75000");
key.SetValue("margin_right", "0.75000");
break;
}
}
}
}
As you notice, I have a webbrowserhelper that is another class. It is quite big and I will not paste it. However, I input the links in comment next to it where you can get the code. The webbrowser itself has a major cache flaw and even if you force it to refresh the page it will always grab the cache, therefore, a clearcache is in order. I learned the hard way.
I hope this helps everyone out there. Please note that this is for .net 4.0.
精彩评论