在SQL Server中嵌入PDF文档到报表

在Web应用程序中,将PDF文档嵌入到报表中是一种常见的需求。例如,当内容作者上传文档以支持他们输入到Web应用程序中的内容时,这种方法非常有效。一个典型的用例是附录,如果打印单独的文档,则无法实现页眉/页脚一致性或页码编号。本文将介绍如何使用TallComponents.PDF.Rasterizer将PDF解析为图像列表,并在Reporting Services (RS)报告中嵌入这些图像。

Reporting Services (RS)工具栏中提供的控件不允许将PDF文档添加到报告中。为了满足这一需求,通过引用TallComponents PDF Rasterizer在C#程序集中解析PDF为图像列表。然后,使用SQL CLR表值函数将程序集包装起来,返回图像数据集。这样,就可以使用Reporting Services的Tablix和Image控件在RS报告中渲染图像列表。

使用代码

首先,创建一个数据库来存储PDF文档并托管CLR程序集。使用Visual Studio Data Dude来创建数据库。

CREATE TABLE [Document] ( [Name] nvarchar(255) NOT NULL, [Extension] nvarchar(10) NOT NULL, [Document] image NOT NULL ) GO

接下来,需要在SQL CLR函数中使用这个表。

CREATE PROCEDURE GetDocument @Name nvarchar(255) AS SELECT [Document] FROM [Document] WHERE [Name] = @Name GO

然后,将测试文档直接插入到数据库中,而不是通过Web应用程序加载。

INSERT INTO [Document] ([Name], [Extension], [Document]) SELECT 'myPDF' AS [Name], 'pdf' AS [Extension], * FROM OPENROWSET( BULK 'C:\CodeCamp2009.pdf', SINGLE_BLOB) AS [Document] GO

测试否可以从数据库中检索文档。

EXEC GetDocument 'myPDF' GO

启用SQL实例的CLR。

EXEC sp_configure 'clr enabled', '1' GO RECONFIGURE GO

接下来,创建程序集以将PDF解析为图像列表。SQL 2008 CLR是.NET 2.0,所以将PDFParser程序集设置为使用.NET 2.0运行。

使用TallComponents的示例应用程序ConvertToImage作为解析函数的模板。然后,向示例中添加了一个按钮以测试解析程序集。以下是PDFParser程序集的代码。

using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using TallComponents.PDF.Rasterizer; using System.IO; namespace PDFParser { public class Parse { public List<Image> Split(byte[] document) { Document pdfDoc = new Document(new BinaryReader(new MemoryStream(document))); Page page = null; List<Image> returnVal = new List<Image>(); for (int i = 0; i < pdfDoc.Pages.Count; i++) { page = pdfDoc.Pages[i]; using (Bitmap bitmap = new Bitmap((int)page.Width, (int)page.Height)) { Graphics graphics = Graphics.FromImage(bitmap); graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; page.Draw(graphics); returnVal.Add((Image)bitmap.Clone()); } } return returnVal; } } }

以下是用来测试上述程序集的代码。

private void cmdTestPDFParser_Click(object sender, EventArgs e) { FileStream fs = new FileStream(@"C:\CodeCamp2009.pdf", FileMode.Open); byte[] pdf = new byte[fs.Length]; fs.Read(pdf, 0, (int)fs.Length); PDFParser.Parse parser = new PDFParser.Parse(); List<Image> images = parser.Split(pdf); }

现在,有一个程序集可以将PDF文档解析为图像列表。接下来,将这个程序集包装在SQL CLR函数中。为了使SQL CLR函数能够使用引用,需要在SQL数据库中注册它及其所有依赖项,所以将运行以下SQL。

将TallComponents.PDF.Rasterizer.dll复制到'C:\Program Files\MicrosoftSQL Server\MSSQL10.SQL2008\MSSQL\Binn\TallComponents.PDF.Rasterizer.dll'。

CREATE ASSEMBLY [System.Drawing] FROM 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll' WITH PERMISSION_SET = UNSAFE CREATE ASSEMBLY [System.Web] FROM 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Web.dll' WITH PERMISSION_SET = UNSAFE CREATE ASSEMBLY [TallComponents.PDF.Rasterizer] FROM 'C:\Program Files\Microsoft SQL Server\MSSQL10.SQL2008\MSSQL\Binn\TallComponents.PDF.Rasterizer.dll' WITH PERMISSION_SET = UNSAFE CREATE ASSEMBLY [PDFParser.Parse] FROM 'C:\Program Files\Microsoft SQL Server\MSSQL10.SQL2008\MSSQL\Binn\CodeCamp2009\PDFParser.dll' WITH PERMISSION_SET = UNSAFE

在SQL项目属性中,进行以下更改:数据库选项卡,设置为Unsafe,设置所有者为dbo。

现在已经设置了SQL CLR环境,并创建并注册了依赖项,将编写包装表值函数。

using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.Security.Permissions; using System.Collections; using System.Drawing; using System.Collections.Generic; using System.IO; using System.Drawing.Imaging; [assembly: System.Security.AllowPartiallyTrustedCallers, FileIOPermission(SecurityAction.RequestMinimum, Unrestricted = true)] namespace SQLCLR { [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Assert, Unrestricted = true)] public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction(FillRowMethodName = "GetPDF_FillRow", TableDefinition = "PDFPageImage Varbinary(max)", DataAccess = DataAccessKind.Read)] public static IEnumerable GetPDF(SqlString DocumentName) { ArrayList items = new ArrayList(); List<Image> pages = new List<Image>(); object[] images; images = new object[1]; MemoryStream pageStream = new MemoryStream(); PDFParser.Parse pdfParser = new PDFParser.Parse(); using (SqlConnection conn = new SqlConnection("context connection = true")) { conn.Open(); SqlPipe pipe = SqlContext.Pipe; SqlCommand cmd = new SqlCommand("GetDocument", conn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add(new SqlParameter("@Name", DocumentName)); SqlDataReader reader = cmd.ExecuteReader(); byte[] pdfContent = null; while (reader.Read()) { pdfContent = (byte[])reader.GetSqlBinary(0); pages = pdfParser.Split(pdfContent); for (int i = 0; i < pages.Count; i++) { MemoryStream ms = new MemoryStream(); pages[i].Save(ms, ImageFormat.Png); items.Add((SqlBinary)ms.ToArray()); } } reader.Close(); reader = null; pdfContent = null; } return items; } private static void GetPDF_FillRow(Object obj, out SqlBinary sItem) { SqlBinary sTemp = (SqlBinary)obj; sItem = sTemp; } } }

部署SQL CLR项目后,可以在SQL Management Studio中使用以下查询进行测试:

select * from dbo.GetPDF('myPDF')

应该得到类似于此的数据集的图像数据。

在报告中使用数据集

创建一个RS项目并添加一个报告。使用向导与查询select * from dbo.GetPDF('myPDF')。将详细文本框替换为矩形,然后是一个Image控件,并调整大小以填充页面。层次结构应该是Tablix\Rectangle\Image,使用文档大纲视图(Ctrl + Alt + T)进行检查。

设置图像属性:

  • 图像源 = 数据库
  • 字段 = PDFPageImage
  • MIME类型 = png

点击预览以查看PDF文档转换为一组图像,供报告使用。

注意事项

  • PDF TallComponents Rasterizer:
    • 优点:扩展性好。
    • 缺点:第三方,不确定所有可以放置在PDF文档中的内容都能被渲染,以及Adobe更新的集成速度。
  • Adobe SDK:
    • 优点:对所有PDF内容都有信心。
    • 缺点:完整的SDK价格昂贵,使用桌面DLL在服务器环境中不受支持。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485