【问题标题】:Printing using c#/ASP.NET MVC2使用 c#/ASP.NET MVC2 打印
【发布时间】:2010-10-19 05:27:49
【问题描述】:

我正在为一家小型企业开发 Intranet ASP.NET MVC2 应用程序。小型企业有多种类型的打印机,根据需要,打印请求将(从浏览器/用户)发送到服务器,服务器将相应地将打印作业发送到正确的打印机。请注意,这对他们来说是一个全新的环境,我几乎可以控制一切。我很可能会使用非常轻量级的操作系统(可能是 Asus ExpressGate 或 Chrome 操作系统,具体取决于发布日期?),因此用户无法安装任何打印机,但服务器将完成所有设置。

这是我的问题:

有没有一种简单的方法可以通过使用 html 链接作为参数并保持 HTML 格式从服务器端打印页面(当然没有对话框,因为不会有人等待点击它们)当然。

我已经看到了 COM 的一些可能性,但如果有可能通过使用 .net 类来避免这种情况,我将不胜感激。我正在使用.net 4.0。但是,即使它是基于 COM 的,我也会接受任何建议。

编辑:请注意,任何有意义的解决方法也将被考虑在内,一个快速(尚未研究)的示例是将此 html 传输到 doc 文件并将此文件发送到打印机。

Edit-Code 因缺乏使用而被取消。

编辑2: 点击此链接:Print html document from Windows Service in C# without print dialog

Vadim 的圣杯解决方案确实有效。但是,它具有仅使用默认打印机等限制。我在打印之前修改了默认打印机,这会导致打印到正确的打印机。我可以看到这里发生了一些并发问题,但到目前为止,这是我想出的最好的(大部分代码来自 Vadim,我完全相信他):

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

}

【问题讨论】:

    标签: asp.net-mvc-2 printing c#-4.0


    【解决方案1】:

    几篇可能对阅读有帮助的文章是:

    如果我发现任何其他内容,将更新。希望这会有所帮助。

    【讨论】:

    • 是的,我目前有一个打印机助手,我在 stackoverflow 中找到了它,我在上面添加了它作为编辑。不幸的是,到目前为止,这并不能帮助我打印 HTML 呈现的文件。
    • 我没看过但是有没有办法打印一个流?如果是这样,那么您可能会创建 html,然后打印包含 html 的响应流......只是一个疯狂的想法:)
    • 使用我在那里显示的代码,它确实有效。对于这个项目的需要,它之所以有效,是因为它是一家小企业。我可能会重新考虑这是否会因为同时更改默认打印机而被更多地使用。我讨厌使用 COM 对象,这需要在服务器上安装 IE,但必须做你必须做的事情,嗯?我仍然愿意接受其他更具扩展性的建议!
    【解决方案2】:

    这是一个仅适用于 .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,它是另一个类。它很大,我不会粘贴它。但是,我在旁边的注释中输入了链接,您可以在其中获取代码。 webbrowser 本身有一个主要的缓存缺陷,即使你强制它刷新页面,它也会总是抓取缓存,因此,一个 clearcache 是有序的。我学会了艰难的方式。

    我希望这对大家有帮助。请注意,这是针对 .net 4.0 的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-26
      • 1970-01-01
      • 1970-01-01
      • 2011-08-19
      • 1970-01-01
      相关资源
      最近更新 更多