Печать с использованием c # / ASP.NET MVC2 - PullRequest
0 голосов
/ 19 октября 2010

Я разрабатываю приложение ASP.NET MVC2 для интрасети для малого бизнеса.Малый бизнес имеет несколько типов принтеров, и в зависимости от того, что требуется, запрос на печать будет отправлен (из браузера / пользователя) на сервер, и сервер соответственно отправит задание на печать нужному принтеру.Обратите внимание, что для них это совершенно новая среда, и я могу контролировать практически все.Я, скорее всего, буду использовать очень легковесную ОС (может быть, Asus ExpressGate или Chrome OS в зависимости от даты запуска?), Поэтому у пользователей не может быть установлено никаких принтеров, но на сервере будет все настроено.

Вот мой вопрос:

Существует ли простой способ напечатать со стороны сервера (конечно, без диалога, потому что никто не будет ждать, чтобы щелкнуть по ним) страницу, используя ссылку html в качестве параметра и сохраняяКонечно, в формате HTML.

Я видел несколько возможностей использования COM, но если есть возможность избежать этого с помощью класса .net, я был бы признателен.Я использую .net 4.0.Однако я приму любые предложения, даже если он основан на COM.

Редактировать: Обратите внимание, что любой обходной путь, который имеет смысл, также будет принят во внимание, быстрый (еще не изученный) примербыло бы перенести этот html в файл документа и отправить этот файл на принтер.

Код редактирования снят из-за неиспользования.

Edits2: После этогоссылка: Печать HTML-документа из Windows Service в C # без диалогового окна печати

Решение святого Грааля Вадима работает.Тем не менее, он имеет ограничения, такие как использование только принтера по умолчанию.Я изменяю принтер по умолчанию до того, как произойдет печать, поэтому печать отправляется на нужный принтер.Я вижу, что здесь происходят некоторые проблемы с совпадением, но пока это лучшее, что я придумал (большая часть этого кода от Вадима, и я полностью ему за это благодарен):

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

}

Ответы [ 2 ]

0 голосов
/ 11 ноября 2010

Это параллельный помощник только для .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 перечисления, одно для управления веб-браузером 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, который является другим классом.Это довольно большой, и я не буду вставлять его.Тем не менее, я добавляю ссылки в комментариях рядом с ним, где вы можете получить код.У самого веб-браузера есть большой недостаток кэша, и даже если вы заставите его обновить страницу, он всегда будет захватывать кэш, поэтому очистка кэша в порядке.Я узнал на своей шкуре.

Надеюсь, это всем поможет.Обратите внимание, что это для .net 4.0.

0 голосов
/ 19 октября 2010

Несколько статей для чтения, которые могут быть полезны:

Будет ли обновление, если я найду что-нибудь еще. Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...