服务器端C#中的HTML或PDF打印

4
我知道这是许多论坛和博客上已知的话题。我读了很多文章,其中很多都很有启发性。但对我来说,似乎需要新的方法来完成这个任务。
我正在寻找在服务器端打印HTML的解决方案。但是在尝试了许多选项之后,我意识到我们
  • 不能给出打印机名称或
  • 它会像txt文件一样打印HTML原始内容
后来得知Ghostscript(https://dev59.com/CXE85IYBdhLWcg3wzm9p#2600189)可以用于在服务器端静默打印PDF。
还尝试使用Crystal Report(但如何动态传递HTML内容给它,即使它不支持许多标签),itextsharp,ssrs,pdfsharp等等,但它们都不支持许多HTML标记和W3C标准。因此,我最终只能生成PDF。只有wkhtmltopdf完美地将HTML转换为PDF。与我的经验相比,它支持每个HTML标记,不像任何其他标记。但是,打印PDf是我多年来的问题。
但现在我甚至面临着使用GhostScript(我正在使用版本9.05)的问题。在localhost上,我可以完美地使用它。我可以在服务器端静默地打印来自UI的任何打印机名称。但是使用IP地址或计算机名称时,它不起作用。我甚至实现了模拟。即使在调用GhostScript时,该进程也会挂起。
现在我想要澄清的是:
  • 是否可能在服务器端打印HTML或PDF(实际内容)?
  • 是否有任何开源工具可以实现这一点?
  • 我想动态传递打印机名称。
任何线索或解决方法都可能帮助全球许多人节省时间。:)
提前感谢您。
问候, Pavan N
在使用Lau的建议后,我能够在命令提示符中完成操作(意味着cmd.exe在我的帐户下运行)。但是我的应用程序将在网络服务下运行。 现在我遇到了一些问题,就像这个ACCESS Denied一样。
是的。最终我能够启动该进程。我能够看到我的gswin32c.exe进程在任务管理器中以我的域凭据运行。代码如下:
public bool PrintVSPDF(string ghostScriptPath, int numberOfCopies, string printerName, string pdfFileName)
{
    Logger.AddToLog("printerName", printerName);
    string impersonationUsername = "";
    string impersonationDomain = "";
    string impersonationPWD = "";

    if (ConfigurationManager.AppSettings["UName"] != null)
    {
        impersonationUsername = Encryption.Decrypt(ConfigurationManager.AppSettings["UName"].ToString(), Encryption.DEFAULT_KEY, Encryption.DEFAULT_SEED);
        impersonationDomain = impersonationUsername.Split('\\').Count() > 1 ? impersonationUsername.Split('\\')[0] : "";
        impersonationUsername = impersonationUsername.Split('\\').Count() > 1 ? impersonationUsername.Split('\\')[1] : impersonationUsername.Split('\\')[0];
    }

    if (ConfigurationManager.AppSettings["PD"] != null)
    {
        impersonationPWD = Encryption.Decrypt(ConfigurationManager.AppSettings["PD"].ToString(), Encryption.DEFAULT_KEY, Encryption.DEFAULT_SEED);
    }

    using (Impersonation imp = new Impersonation(impersonationUsername, impersonationDomain, impersonationPWD))
    {
        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.Arguments = "-dPrinted -dNoCancel -dNOPAUSE -dBATCH -dNumCopies=" + Convert.ToString(numberOfCopies) + "  -sDEVICE=mswinpr2 -sOutputFile=%printer%\"" + printerName + "\" \"" + pdfFileName + "\" ";
        startInfo.FileName = ghostScriptPath;
        startInfo.UseShellExecute = false;
        startInfo.CreateNoWindow = true;
        //startInfo.RedirectStandardInput = true;
        startInfo.RedirectStandardError = true;
        startInfo.RedirectStandardOutput = true;
        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        startInfo.UserName = impersonationUsername;
        startInfo.Domain = impersonationDomain;
        SecureString ss = new SecureString();
        for (int i = 0; i < impersonationPWD.Length; i++)
        {
            ss.AppendChar(impersonationPWD[i]);
        }
        startInfo.Password = ss;
        Process process = null;
        try
        {
            process = Process.Start(startInfo);
            //Logger.AddToLog("Error VS", process.StandardError.ReadToEnd());
            //Logger.AddToLog("Output VS", process.StandardOutput.ReadToEnd());
            //Logger.AddToLog(process.StartInfo.Arguments.ToString(), "VS Print Arguments");
            //Console.WriteLine(process.StandardError.ReadToEnd() + process.StandardOutput.ReadToEnd());
            //Logger.AddToLog(process.StartInfo.FileName.ToString(), "VS Print file name");
            process.WaitForExit(30000);
            if (process.HasExited == false) 
                process.Kill();
            int exitcode = process.ExitCode;
            process.Close();
            return exitcode == 0;
        }
        catch (Exception ex)
        {
            Logger.AddToLog(ex);
            return false;
        }
    }
}

但是在本地主机的5030端口上运行,该过程完美执行。但是使用IP地址或机器名称时,它会卡住并抛出此错误。

对于Adobe Reader、Foxit等软件也发生了同样的情况。

( Process must exit before requested information can be determined. :    at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.get_ExitCode() )

我对你所说的“服务器端打印”感到困惑。打印机连接在哪里?你是指从远程Web服务器启动打印到用户本地打印机吗? - Joe
“打印服务器端” 意味着我正在尝试在服务器端(即IIS)上打印由XML+XSL生成的HTML字符串。是的,打印机连接到了IIS服务器。这是一个内部网络应用程序。 - Pavan N
2个回答

3
我一直在致力于一个与此相关的项目,但这是非常令人沮丧的经历。我发现最可靠的方法是将报告导出为PDF,然后使用Foxit Reader(由于安全问题而不使用Adobe Reader),通过Diagnostics.Process打印文档。
打印机名称可以提供给Foxit Reader命令行参数。
我使用的环境是在Windows Server 2008 R2 x64上运行的ASP.net 3.5和IIS 7,还使用Sql Server Reporting Services。
也许这段代码能帮助你:
    public FileContentResult GetPOReport(DataTable reportData, int poNumber, string copies, out string fileName, out string downloadPath)
    {
        fileName = "PO_" + poNumber.ToString().Trim() + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".pdf";
        downloadPath = "/Generated/" + fileName;

        var outputFiles = new Dictionary<string, string>
                              {
                                  {"", Server.MapPath("~" + downloadPath)}
                              };

        if (!string.IsNullOrWhiteSpace(copies))
        {
            var copyList = copies.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var temp in copyList)
                outputFiles.Add(temp, Server.MapPath("~" + "/Generated/" + temp.Trim() + ".pdf"));
        }

        FileContentResult returnFile = null;

        foreach (var outputFile in outputFiles)
        {
            var file = WriteReportToDisk(reportData, outputFile.Value, outputFile.Key);

            if (file == null)
                continue;

            if (string.IsNullOrWhiteSpace(outputFile.Key))
                returnFile = file;
            else
                PrintReport(outputFile.Value);
        }

        return returnFile;
    }

    public void PrintReport(string filePath)
    {
        try
        {
            if (!ConfigurationManager.AppSettings.AllKeys.Contains("AdobeReaderPath") ||
                !ConfigurationManager.AppSettings.AllKeys.Contains("AdobePrintParameters") ||
                !ConfigurationManager.AppSettings.AllKeys.Contains("PrinterName"))
                return;

            var adobeReaderPath = ConfigurationManager.AppSettings["AdobeReaderPath"].Trim();
            var adobePrintParameters = ConfigurationManager.AppSettings["AdobePrintParameters"].Trim();
            var printerName = ConfigurationManager.AppSettings["PrinterName"].Trim();
            var printProcessDomain = ConfigurationManager.AppSettings["PrintProcessDomain"].Trim();
            var printProcessUserName = ConfigurationManager.AppSettings["PrintProcessUserName"].Trim();
            var printProcessPassword = ConfigurationManager.AppSettings["PrintProcessPassword"].Trim();

            var userPrinter = Entities.UserPrinters.FirstOrDefault(p => p.UserName == User.Identity.Name);

            if (userPrinter != null)
                printerName = userPrinter.PrinterName.Trim();

            using (var process = new Process
            {
                StartInfo =
                    new ProcessStartInfo(
                    adobeReaderPath,
                    string.Format(adobePrintParameters, filePath, printerName)
                    )
            })
            {
                if (!string.IsNullOrWhiteSpace(printProcessUserName))
                {
                    if (!string.IsNullOrWhiteSpace(printProcessDomain))
                        process.StartInfo.Domain = printProcessDomain;

                    process.StartInfo.UserName = printProcessUserName;

                    var securePassword = new SecureString();

                    foreach (var passwordCharacter in printProcessPassword)
                        securePassword.AppendChar(passwordCharacter);

                    process.StartInfo.Password = securePassword;

                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.CreateNoWindow = true;

                    process.StartInfo.LoadUserProfile = true;
                }

                process.Start();

                process.WaitForExit(30000);
            }
        }
        catch (Exception exception)
        {
            EventLog.WriteEntry("PO Suggestion Viewer", string.Format("PO Suggestion Viewer Error:\n{0}", exception.Message));
            throw;
        }
    }

    public FileContentResult WriteReportToDisk(DataTable reportData, string filePath, string copy)
    {
        var webReport = new WebReport()
        {
            ExportFileName = "PO Report",
            ReportPath = Server.MapPath("~/Reports/PurchaseOrderReport.rdlc")
        };

        if (!string.IsNullOrWhiteSpace(copy))
            webReport.ReportParameters.Add(new ReportParameter("Copy", copy));

        if ((User != null) && (User.Identity != null) && (User.Identity.Name != null))
            webReport.ReportParameters.Add(new ReportParameter("User", User.Identity.Name));

        webReport.ReportDataSources.Add(new ReportDataSource("ReportData", reportData));

        var report = webReport.GetReport();

        Response.AddHeader("content-disposition", string.Format("attachment; filename={0}.{1}", webReport.ExportFileName, webReport.FileNameExtension));
        Response.ContentType = "application/pdf";

        var file = File(report, webReport.MimeType, "POReport");

        System.IO.File.WriteAllBytes(filePath, file.FileContents);

        return file;
    }

我的 web.config 包含:

<appSettings>
    <add key="webpages:Version" value="1.0.0.0" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="AdobeReaderPath" value="C:\Program Files (x86)\Foxit Software\Foxit Reader\Foxit Re-ader.exe" />
    <add key="AdobePrintParameters" value="-t &quot;{0}&quot; &quot;{1}&quot;" />
    <add key="PrinterName" value="HP_Office" />
    <add key="PrintProcessDomain" value="DOMAIN_NAME" />
    <add key="PrintProcessUserName" value="DOMAIN_USER" />
    <add key="PrintProcessPassword" value="DOMAIN_PASSWORD" />
</appSettings>

似乎对我有所帮助。我会谷歌一下并找到一些相关的C#代码。谢谢Lau。 - Pavan N
1
我也需要同样的东西。我通过向报告发送要生成的副本数量来让报告处理页面数。 - Dusty Lau
这似乎有点不正规。但我很惊讶地发现,在服务器端打印方面支持非常少。显然,这并不是经常做的事情。 - Dusty Lau
是的 Dusty。严肃地说……它得到的支持很少。非常感谢您的支持。至于您的代码,我尝试了通过命令提示符执行它 <<<<<C:\Program Files\Foxit Software\Foxit Reader>"Foxit Reader.exe" /t C:/1.pdf \ed t-115\HP Officejet 4500 G510>>>>> 在我的 Vista 机器上它什么也没做。但在 Win2003 上可以打印。我也尝试过你的代码。猜测有些模仿的问题。它在启动进程时给我“拒绝访问”的错误(就像我在GhostScript中面对的一样)。在Win2K3中它会抛出错误,但不会记录在我的文本文件中。现在我一无所知。 - Pavan N
你能尝试以管理员身份运行应用程序吗?尝试将应用程序池的用户更改为完整域管理员。 - Dusty Lau
显示剩余6条评论

1
抱歉发布晚了。我以为我已经回答了这个问题。 我找到了一种将HTML转换为PDF的解决方法。 我正在使用WKHTMLTOPDF API将HTML转换为PDF。与许多商业产品相比,它看起来非常棒。我能够获得彩色/灰度、边距、索引等等。 这是我遵循的链接 http://code.google.com/p/wkhtmltopdf/
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = AppDomain.CurrentDomain.BaseDirectory + @"\bin\wkhtmltopdf.exe";
pdfFile = localReportPath + "\\Reports\\Savedfiles\\" + filename + ".pdf";
//Ref: http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html
startInfo.Arguments = " --minimum-font-size 16 --margin-left 10mm --margin-right 10mm --zoom 3 " + htmlFile + " " + pdfFile;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process p = Process.Start(startInfo);
p.WaitForExit();
p.Dispose();
p.Close();

我也发送了同样的内容给Ghostscript,以获取一个漂亮的TIFF文件用于传真。即使处理大量数据,性能也很好。

敬礼, Pavan N


1
对我来说非常好用,甚至可以从https链接下载图片并将其包含在内... 将文档大小适当调整以适应页面宽度(而其他工具如CutePDF打印机则没有)。 - TCC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接