2010-10-19 101 views
0

我正在开发一个小型企业的Intranet ASP.NET MVC2应用程序。小型企业具有多种类型的打印机,并且根据需要将打印请求(从浏览器/用户)发送到服务器,并且服务器将相应地将打印作业分派到合适的打印机。请注意,这是一个全新的环境,我几乎可以控制所有的东西。我很可能会使用一个非常轻量级的操作系统(也许Asus ExpressGate或Chrome OS取决于发布日期?),因此用户不能安装任何打印机,但服务器将安装一切。使用c#/ ASP.NET MVC2打印

这里是我的问题:

有没有一种简单的方法来从服务器端进行打印(当然没有对话,因为不会有任何人等着点击它们)使用HTML链接的页面一个参数并保持HTML格式当然。

我见过的COM东西几种可能性在那里,但如果有可能通过使用.NET类我希望避免这种情况。我正在使用.net 4.0。不过,我会采取任何建议,即使它是基于COM的。

编辑:请注意,这是有道理的任何变通办法也将被考虑在内,快速(非研究还)例子是这个网站转移到一个doc文件并将该文件发送到打印机。

编辑码起飞的缺乏使用。

Edits2: 以下网址:Print html document from Windows Service in C# without print dialog

瓦迪姆的圣杯解决方案确实工作。但是,它具有仅使用默认打印机的限制。我在打印发生之前修改了默认打印机,这导致打印进入正确的打印机。我可以看到在这里发生,但一些并发问题,到目前为止,这是我想出的最好的(大多数代码是由瓦迪姆,我给了他这样做的全部学分):

/// <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.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; 
     } 
    } 
} 
    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; 
    } 

} 

回答

0

这是.NET 4.0并行助手只管理碰撞/线程。

/// <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; 
     } 
    } 
} 

我用2个枚举,一个是Web浏览器控件的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 
} 

另一种是做任何你需要打印自定义:

public enum PrintDocumentType 
{ 
    Bill, 
    Label //etc... 
} 

现在,这里是我使用的打印机助手,它设置了默认打印机(并打印到它),也根据我需要打印的内容更改页边距:

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; 
      } 
     } 
    } 
} 

正如你所看到的,我有一个webbrowserhelper是另一个类。这是相当大的,我不会粘贴它。但是,我在其旁边的注释中输入链接,您可以在其中获取代码。网页浏览器本身有一个主要的缓存漏洞,即使你强制它刷新页面,它总是会抓取缓存,因此,一个clearcache是​​为了。我学会了艰难的道路。

我希望这可以帮助所有人。请注意,这是为.NET 4.0。

0

一对夫妇的文章readng这可能是有帮助的:如果我发现别的

将更新。希望这可以帮助。

+0

是的我目前有一个打印机助手,我在这里找到了我在上面作为编辑添加的stackoverflow。不幸的是,这并不能帮助我打印HTML呈现的文件。 – 2010-10-20 02:06:22

+0

我还没看,但有没有办法打印流?如果是这样,那么你可以潜力得到创建的HTML,然后打印包含html的响应流......只是一个疯狂的想法:) – WestDiscGolf 2010-10-20 11:20:46

+0

使用我在那里显示的代码,它确实有效。对于这个项目的需求,因为它是一个小企业。我可能会重新考虑这是由于与更改默认打印机的同时发生而将被使用更多的东西。我讨厌使用COM对象,这需要在服务器上安装IE,但是要做你应该做的事情?我仍然接受其他更具扩展性的建议! – 2010-10-21 23:14:50