[转]为ReportViewer导出的PDF文档加上水印

接到一個頗富挑戰性的需求,Reporting Service或RDLC報表可匯出成Excel、PDF等檔案格式,對一般麻瓜型使用者而言,PDF唯讀,Excel則可修改,業務單位希望在拿到報表紙本時加以區分;換句話說,如果能讓PDF與Excel檔的列印結果有別,即可做為報表結果是否唯讀,有無被修改可能的依據。(姑且排除使用者設法修改PDF檔或將Excel仿製成PDF樣式的情境)

我想到一個做法是為匯出的PDF檔加上浮水印。同一張報表匯出的Word、Excel、PDF檔內容理應一致,當PDF檔被加註浮水印,便足以形成區隔。在PDF檔加上浮水印非難事,用iTextSharp應可搞定,但匯出PDF檔的過程本屬ReportViewer內部運作,不容外人插手,要在匯出PDF檔時動手腳需要點Hacking,好一個讓程式魔人熱血沸騰的挑戰!

分析問題的第一步,先剖析ReportViwer匯出PDF檔的原理:

當執行PDF匯出動作時,實際上是呼叫ReportViewer加掛的HttpHandler,web.config可以看到相關設定:

<add path="Reserved.ReportViewerWebControl.axd" verb="*" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" validate="false" />

在網頁按匯出鈕時,瀏覽器被導向特定URL,傳入OpMode=Export、Format=PDF參數,由HttpHandler傳回當下檢視報表的PDF檔。如要在此過程動手腳,有個不錯的切入點是透過Global.asax或HttpModule攔截BeginRequest事件,遇到呼叫Reserved.ReportViewerWebControl.axd匯檔案時加入自訂邏輯,修改要傳回的檔案內容。但ReportViewer的HttpHandler在PDF檔產生後便立刻寫入HttpRepsonse傳回客戶端,輪不到我們插手,因此下一個挑戰是如何攔截並修改其內容。

此時,ASP.NET的另一個好用機制派上用場: Response.Filter,它允許我們在HttpResponse將結果byte[]寫入輸出Stream之前,先交由我們自訂的Stream物件處理,可以實現修改後再傳至客戶端的目的。

排版顯示純文字

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
 
public class ExpFileFilterStream : MemoryStream
{
    private Stream output = null;
    Func<byte[], byte[]> modifier = null;
    private HttpResponse response = null;
    private bool firstFlush = false;
    public ExpFileFilterStream(HttpResponse resp, Func<byte[], byte[]> modifier)
    {
        response = resp;
        output = resp.Filter;
        this.modifier = modifier;
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        //由於ReportViewer會關閉BufferOutput,並分成多段Flush傳回前端,
        //在此重新啟用Buffer功能(因必須得到檔案完整內容再處理),
        //但會漏掉第一次的Flush(),藉以以下邏輯避免第一次部分Flush()
        //註: ReportViewer在分段Flush的大小為81920,當少於此值表示不需略過Flush
        if (!response.BufferOutput && count == 81920)
        {
            response.BufferOutput = true;
            firstFlush = true;
        }
        base.Write(buffer, offset, count);
    }
    public override void Flush()
    {
        if (firstFlush)
        {
            firstFlush = false;
            return;
        }
        //Flush時,將要傳回內容byte[]交由外部邏輯處理後再取回
        byte[] buff = base.ToArray();
        if (modifier != null)
            buff = modifier(buff);
        output.Write(buff, 0, buff.Length);
    }
}

我寫了一個簡單的Filter Stream物件,原理是在Write()時先蒐集ReportViewer HttpHandler要傳回的檔案內容,當Flush()要傳回結果時,將先前接收到的PDF檔案內容(byte[])交由外部邏輯,Func<byte[], byte[]>,進行加蓋浮水印處理,再傳回修改版檔案到真正的OutputStream。

其中有小技巧: ReportViewer HttpHandler為了減少記憶體耗用及提高回應效率,會將Response.BufferOutput設為false,讓匯出檔案內容分成多段Flush()傳回(每段不超過81920 bytes)。由於我們需要接收完整檔案才能進行修改並一次回傳,故不容先傳回部分未修改內容的情形發生。在Write()將Response.BufferOutput改回true即可偷偷取消分段傳回,唯此時第一個分段的Flush()已箭在弦上,故要用一個firstFlush旗標避開第一次Flush()。之後因Response.BufferOutput已被設為true,會等到全部的PDF檔都透過Write()寫入才呼叫Flush(),此時MemoryStream所保存的便是完整PDF檔內容。

在BeginRequest事件加掛Response.Filter的工作,則寫成一個HttpModule。程式很單純,較花工夫的是透過iTextSharp在PDF左上角印上PDF yyyy/MM/dd HH:mm:ss樣式的半透明浮水印,iText歷史悠久、功能強大,網路上不難找到現成範例,很順利就完成了。

排版顯示純文字

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Web;
using iTextSharp.text.pdf;
using iText = iTextSharp.text;
using iTextSharp.text;
using System.IO;
 
namespace ReportViewerHacking
{
    public class WaterMarkModule : IHttpModule
    {
        #region IHttpModule Members
 
        public void Dispose()
        {
        }
 
        public void Init(HttpApplication context)
        {
            context.BeginRequest += context_BeginRequest;
        }
 
        void context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            string url = app.Context.Request.RawUrl;
            var context = app.Context;
            if (url.Contains("Reserved.ReportViewerWebControl.axd"))
            {
                var req = context.Request;
                var resp = context.Response;
                string opType = req["OpType"];
                string name = req["Name"];
                string format = req["Format"];
                if (opType == "Export" && format == "PDF")
                {
                    resp.BufferOutput = true;
                    resp.Filter = new ExpFileFilterStream(resp, (buff) =>
                    {
                        //輸入PDF內容,加上浮水印
                        PdfReader pr = new PdfReader(buff);
                        iText.Rectangle dimension = pr.GetPageSize(1);
                        MemoryStream ms = new MemoryStream();
                        PdfStamper stmp = new PdfStamper(pr, ms);
                        //REF: http://bit.ly/10qirzK
                        BaseFont bf =
                            BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
                        iText.Font fnt = new iText.Font(bf, 6, iText.Font.NORMAL, BaseColor.BLACK);
                        PdfContentByte cb = stmp.GetOverContent(1);
                        //設定半透明文字
                        PdfGState gstate = new PdfGState();
                        gstate.FillOpacity = 0.2f;
                        gstate.StrokeOpacity = 0.2f;
                        cb.SetGState(gstate);
                        cb.BeginText();
                        cb.SetFontAndSize(bf, 6);
                        cb.SetColorFill(BaseColor.BLACK);
                        cb.ShowTextAligned(PdfContentByte.ALIGN_LEFT,
                            string.Format("PDF {0:yyyy-MM-dd HH:mm:ss}", DateTime.Now),
                            dimension.GetLeft(1), dimension.GetTop(5), 0);
                        cb.EndText();
 
                        stmp.Close();
                        pr.Close();
                        return ms.ToArray();
                    });
                }
            }
        }
        #endregion
    }
}

將HttpModule掛進ASP.NET網站,之後只要ReportViewer匯出PDF檔,就一律會被偷偷加上浮水印,讓我過了小小當駭客的癮,哈!!

时间: 2024-08-28 14:13:23

[转]为ReportViewer导出的PDF文档加上水印的相关文章

将w3cplus网站中的文章页面提取并导出为pdf文档

最近在看一些关于CSS3方面的知识,主要是平时看到网页中有很多用CSS3实现的很炫的效果,所以就打算系统的学习一下.在网上找到很多的文章,但都没有一个好的整理性,比较凌乱.昨天看到w3cplus网站中关于CSS3的一些文章,觉得讲解的比较细,所以就决定以此作为学习的模板,一步步开始. 平时上下班在地铁上经常是拿着手机看小说新闻之类的,考虑到在手机端直接访问这些网页肯定会耗费很大的流量,所以最好是将这些文章下载下来放在手机里看,比如保存成图片或pdf当然是最好的选择. 之前曾在园子里看到某前端高手

扫描的PDF文档快速导入书签栏

扫描的PDF文档快速导入书签栏 1.  将带标题的Word生成PDF文档,Word导出的PDF文档是带有标签,操作如下: 2.  下载绿色软件FreePic2Pdf,打开选择更改PDF: 3.   首先从PDF中摘取书签,选择Word导出PDF文件,选择新的文件地址生成书签数据: 4.  生成的书签数据如下: 5.  往扫描版PDF挂书签,点击开始: 书签生成,灰常好用. 原文地址:https://www.cnblogs.com/CSZhang/p/12568483.html

java导出pdf文档

java导出pdf文档,多是iText实现的,可以创建pdf文档,并向文档写入内容. 1 导入包:itext-2.0.6.jar       itext必须使用的包. iTextAsian.jar      向pdf写入中文必须的包. 2 代码: package com.exp.pdf; import java.awt.Color; import java.io.FileOutputStream; import com.lowagie.text.Document; import com.lowa

支持将数据导出到Excel文档的时候设置单元格格式的.NET控件Spire.DataExport

Spire.DataExport for .NET是e-iceblue公司推出的一款数据导出类.NET控件.作为一款专业的数据导出控件,Spire.DataExport for .NET可以帮助开发人员轻松快速的从各种主流数据库中导出数据并存储于各种文件格式中.他支持从SQL Command, DataTable,ListView中导出数据并存储于MS Excel,MS Word, HTML, XML, PDF, MS Access, DBF, SQL Script, SYLK, DIF, CS

java操作office和pdf文件java读取word,excel和pdf文档内容

在平常应用程序中,对office和pdf文档进行读取数据是比较常见的功能,尤其在很多web应用程序中.所以今天我们就简单来看一下Java对word.excel.pdf文件的读取.本篇博客只是讲解简单应用.如果想深入了解原理.请读者自行研究一些相关源码. 首先我们来认识一下读取相关文档的jar包: 1. 引用POI包读取word文档内容 poi.jar 下载地址 http://apache.freelamp.com/poi/release/bin/poi-bin-3.6-20091214.zip 

【Win10 开发】读取PDF文档

关于用来读取PDF文档的内容的API,其实在Win8.1的时候就有,不过没关系,既咱们讨论的是10的UAP,连同8.1的内容也包括进去,所以老周无数次强调:把以前的内容学好了,就可以在不学习任何新知识的前提直接进入10的开发,至于你信不信,反正我信了. 好,P话不扯了,今天刚升级了Build 10061,VS创建UAP并运行是没什么问题的,不过XAML设计器貌似不太听话,比较淘气:还有个别API有问题,虽然异常报的是CLR损坏,但我们知道RT其实是基于COM的形式注册的,实际上是Win32的数据

利用Java动态生成 PDF 文档

利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那么目前最佳的解决方案,你可能会想到 iText ,对没错... iText+(Velocity / Freemarker)可以实现.不过据我熟悉,iText本身提供的HTML解析器还是不够强大,许多HTML标签和属性无法识别,更悲催的是简单的CSS它不认识,排版调整样式会让你头大的.不要失望,接下来

一起学微软Power BI系列-官方文档-入门指南(7)发布与共享-终结篇+完整PDF文档

接触Power BI的时间也只有几个月,虽然花的时间不多,但通过各种渠道了解收集,谈不上精通,但对一些重要概念和细节还是有所了解.在整理官方文档的过程中,也熟悉和了解了很多概念.所以从前到后把微软官方的入门稳定粗略的介绍了一下.本篇文章也是这个小系列的终结篇.我将所有的章节整理为了一个完整的PDF文档分享给大家.其实了解之后发现文档还是有些简单的,过程其实没介绍到.因为微软也提供了支持文档,但限于篇幅太大,我就不打算按照原文一个个复制给大家了.入门文档是让大家有个粗略的了解.技术文档就是细节了,

Java iText使用PDF模板生成PDF文档

我们系统需要生成一个可以打印的PDF文档,老板给了我一个Word文档,按照这个Word文档的格式生成PDF文档. 第一步:下载AdobeAcrobat DC,必须使用这个来制作from域. 第二步:使用AdobeAcrobat DC将Word导成PDF文档. 第三步:由于还要加水印的效果,所以还是使用AdobeAcrobat DC来添加水印,非常方便: 添加水印的方法:使用AdobeAcrobat DC打开PDF文档,"工具"->"编辑PDF"->&qu