【问题标题】:ASP.NET Core: Create and Open PDF (in browser)ASP.NET Core:创建和打开 PDF(在浏览器中)
【发布时间】:2019-01-07 21:45:32
【问题描述】:

在 ASP.NET Core 中工作并使用 iTextSharp,我正在构建 PDF 并将其保存到本地系统。我现在想在浏览器中打开该文件,但似乎无法完成这项工作,因为我在一次尝试中遇到了 FileStream 错误,而在另一次尝试中却什么也没有。

我的逻辑在下面的控制器中。我已经用// region description 替换了不必要的代码。重要的代码(我尝试过的东西)在TODO: Open the file 区域内。

控制器

    [HttpPost]
    public (JsonResult, IActionResult) CreatePDF([FromBody] ReportViewModel model)
    {
        try
        {

            // region Logic code
            using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream())
            {
                // Create the document
                iTextSharp.text.Document document = new iTextSharp.text.Document();
                // Place the document in the PDFWriter
                iTextSharp.text.pdf.PdfWriter PDFWriter =
                    iTextSharp.text.pdf.PdfWriter.GetInstance(document, memoryStream);

                // region Initialize Fonts

                // Open the document
                document.Open();

                // region Add content to document

                // Close the document
                document.Close();

                #region Create and Write the file
                // Create the directory
                string directory = $"{_settings.Value.ReportDirectory}\\";
                System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(directory));

                // region Create the fileName

                // Combine the directory and the fileName
                directory = System.IO.Path.Combine(directory, fileName);

                // Create the file
                byte[] content = memoryStream.ToArray();
                using (System.IO.FileStream fs = System.IO.File.Create(directory))
                {
                    fs.Write(content, 0, (int)content.Length);
                }
                #endregion

                #region TODO: Open the file
                // TRY: File(Stream, type) => Newtonsoft.Json.JsonSerializationException:
                // Error getting value from 'ReadTimeout' on 'System.IO.FileStream'.
                // ---> System.InvalidOperationException: Timeouts are supported for this stream.
                System.IO.FileStream fileStream = new System.IO.FileStream(directory, System.IO.FileMode.Open);
                var returnPDF = File(fileStream, contentType: "application/pdf");

                // TRY: File(path, type) => Newtonsoft.Json.JsonSerializationException:
                // Error getting value from 'ReadTimeout' on 'System.IO.FileStream'.
                // ---> System.InvalidOperationException: Timeouts are supported for this stream.
                returnPDF = File(System.IO.File.OpenRead(directory), contentType: "application/pdf" );

                // TRY: File(byte[], type) => Nothing happened
                returnPDF = File(content, contentType: "apllication/pdf");
                #endregion

                return ( Json(new { isError = false }), returnPDF );
            }
        }
        catch (System.Exception ex)
        {
            Serilog.Log.Error($"ReportController.CreatePDF() - {ex}");
            return ( Json(new { isError = true, errorMessage = ex.Message }), null );
        }
    }

参考有用的 Stackoverflow 答案

【问题讨论】:

    标签: c# pdf asp.net-core itext


    【解决方案1】:

    您可以创建一个 FileContentResult 并将已分配的 byte[] 传递给它,而不是再次从目录加载文件。

    return ( Json(new { isError = false }), new FileContentResult(content, "application/pdf"));
    

    请记住,浏览器不会以这种方式下载文件。它将提取响应流但不会下载它,因为它在您的 json 响应中,因此响应内容类型将为 application/json。

    您可以改为只返回一个 IActionResult 并这样做:

    return new FileContentResult(content, "application/pdf");
    

    如果您坚持拥有“isError”信息,那么您可以另外提供一个 url 而不是 fileresult。为此,您可以在 ConfigureServices(...) 方法中注册 IActionContextAccessor 类。

    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
    

    提供单独的路由并使用IUrlHelperFactory 生成一个url。

    可以在here找到样本。

    当响应 isError 等于 false 时,可以在前端以编程方式“点击”此 url。

    【讨论】:

    • 如果我理解您所说的正确加载文件的内容,那么我在执行File(path, "application/pdf") 时会加载它?我实际上并没有用File(Stream, "application/pdf")File(byte[], "application/pdf") 重新加载文件,对吗?那应该和new FileContentResult(content, "application/pdf")一样吗?感谢您澄清这一点!关于其余部分,return new FileContentResult(content, "application/pdf"); 在我的情况下没有做任何事情(与我上次尝试相同)。你说得对,我真的不需要返回isError。我已经修改了代码,非常感谢这个链接。
    • 我的意思是您在内容字节数组中分配了文件字节后手动打开的文件流。如果它不起作用,则您的文件字节必须已损坏。使用静态pdf文件,看看它是否有效。
    【解决方案2】:

    Action 应该返回单个结果,无论是 PDF 还是 JSON,以便客户端可以适当地处理响应。

    我重构了代码,通过将主要逻辑提取到一个单独的函数中,使其更易于阅读。

    操作的主要焦点是如何返回响应。

    [HttpPost]
    public IActionResult CreatePDF([FromBody] ReportViewModel model) {
        try {
            // region Logic code
            byte[] content = CreateFile(model);
            return File(content, contentType: "application/pdf");
        } catch (System.Exception ex) {
            Serilog.Log.Error($"ReportController.CreatePDF() - {ex}");
            return Json(new { isError = true, errorMessage = ex.Message });
        }
    }
    
    byte[] CreateFile(ReportViewModel model) {
        using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream()) {
            // Create the document
            iTextSharp.text.Document document = new iTextSharp.text.Document();
            // Place the document in the PDFWriter
            iTextSharp.text.pdf.PdfWriter PDFWriter =
                iTextSharp.text.pdf.PdfWriter.GetInstance(document, memoryStream);
    
            // region Initialize Fonts
            // Open the document
            document.Open();
            // region Add content to document
            // Close the document
            document.Close();
            #region Create and Write the file
            // Create the directory
            string directory = $"{_settings.Value.ReportDirectory}\\";            
            System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(directory));
            // region Create the fileName
            // Combine the directory and the fileName
            directory = System.IO.Path.Combine(directory, fileName);
            // Create the file
            byte[] content = memoryStream.ToArray();
            using (System.IO.FileStream fs = System.IO.File.Create(directory)) {
                fs.Write(content, 0, (int)content.Length);
            }
            #endregion            
            return content;
        }    
    }
    

    【讨论】:

    • 所以,如果我没有遗漏任何内容:您唯一更改的是端点,它现在分为 2 个不同的功能(包括仅返回一项的端点)?如果不建议返回 2 个值,那为什么 System.ValueTuple 是一个东西?
    • @WouterVanherck 值元组是一个东西,但它并不意味着 MVC 操作结果。
    • @WouterVanherck 第二,如果您查看重构的代码,您将看到该操作如何仅返回 IActionResult 抽象,它可以是 PDF 文件或 JSON 错误响应,但不能同时返回。跨度>
    • 是的,我注意到您只在抛出错误时返回 Json,而在代码成功时没有返回 Json。我现在正在尝试这个,因为我同意我可以在没有 Json 的情况下成功。 E:感谢您的后期编辑,让我意识到我在这里不需要 2 个函数
    • 试过了,没有抛出任何错误,但也没有打开 PDF。当我传递内容时,结果与前面描述的相同
    【解决方案3】:

    这略微改编自我的工作代码,但应该适合您的目的。我相信还有一些改进的空间(比如正确处理 MemoryStream),但它简洁、干净,最重要的是,它有效。

    不需要额外的序列化,如果不是必需的,也不需要读取/写入文件系统。

    控制器

    [HttpPost]
    public (JsonResult, IActionResult) CreatePDF([FromBody] ReportViewModel model)
    {
        try
        {
            return ( Json(new { isError = false }), GetDocument() );
        }
    
        catch (System.Exception ex)
        {
            Serilog.Log.Error($"ReportController.CreatePDF() - {ex}");
            return (Json(new {isError = true, errorMessage = ex.Message}), null);
        }
    }
    
    public FileContentResult GetDocument()
    {
        var fc = new MyPdfCreator();
        var document = fc.GetDocumentStream();
        return File(document.ToArray(), "application/pdf", "File Name.pdf");
    }
    

    MyPdfCreator

    public class MyPdfCreator 
    {
        public MemoryStream GetDocumentStream()
        {
            var ms = new MemoryStream();
            var doc = new Document();
            var writer = PdfWriter.GetInstance(doc, ms);
            writer.CloseStream = false;
            doc.Open();
            doc.Add(new Paragraph("Hello World"));
            doc.Close();
            writer.Close();
            return ms;
        }
    }
    

    【讨论】:

    • 使用 MyPdfCreator 类时:'PdfWriter: type used in a using statement must be implicitly convertible to 'System.IDisposable'
    • 新。道歉。我使用的是PdfStamper,我换成了 PdfWriter。我应该知道的更好。
    • 请查看更新后的代码。我已经按照我的意愿添加了适当的处置。这次我也测试过了。 =P
    • 另外,在更正我的代码时,我注意到 iTextSharp 已过时。自上一个版本的 itextsharp 以来,他们对 itext 进行了完全重写。 Install-Package itext7
    • 当使用你的更新版本时:'Document': type used in a using statement must be implicitly convertible to 'System.IDisposable',你确定它编译了吗? - 另外:iText7 属于 AGPL 许可,而我们使用的是具有 LGPL 许可的版本。我们的代码不会开源
    猜你喜欢
    • 1970-01-01
    • 2021-06-02
    • 2011-06-12
    • 1970-01-01
    • 2015-06-11
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    相关资源
    最近更新 更多