[转]为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檔,就一律會被偷偷加上浮水印,讓我過了小小當駭客的癮,哈!!
[转]为ReportViewer导出的PDF文档加上水印的更多相关文章
- 将w3cplus网站中的文章页面提取并导出为pdf文档
最近在看一些关于CSS3方面的知识,主要是平时看到网页中有很多用CSS3实现的很炫的效果,所以就打算系统的学习一下.在网上找到很多的文章,但都没有一个好的整理性,比较凌乱.昨天看到w3cplus网站中 ...
- rails应用页面导出为pdf文档
1.下载安装wkhtmltox https://wkhtmltopdf.org/downloads.html 2.gemfile添加 gem 'pdfkit' #页面导出pdf gem 'wkht ...
- 如何在PDF文档上加水印
当我们需要传输一些比较重要的文件时,往往会选择将文档转换为PDF文件,避免其他人复制.更改文档的内容. pdfFactory不仅可以为用户提供快速创建PDF的功能,同时还提供了添加水印的功能.有了水印 ...
- ABBYY FineReader 15 中保存和导出PDF文档的小细节
运用ABBYY FineReader OCR文字识别软件,用户能将各种格式的PDF文档保存为新的PDF文档.PDF/A格式文档,以及Microsoft Word.Excel.PPT等格式.在保存与导出 ...
- C#(MVC) Word 替换,填充表格,导出并下载PDF文档
近期做一个关于C# 操作 Word 模板 文档的功能模块,查阅资料,最终完美完成任务,记录下来,以便后面还会用到.
- 使用Spire PDF for .NET将HTML转换成PDF文档
目录 开发环境说明 Spire PDF for .NET (free edition)体验 资源下载 开发环境说明 Microsoft Visual Studio 2013 Ultimate Edit ...
- 利用Java动态生成 PDF 文档
利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...
- 【Win10 开发】读取PDF文档
关于用来读取PDF文档的内容的API,其实在Win8.1的时候就有,不过没关系,既咱们讨论的是10的UAP,连同8.1的内容也包括进去,所以老周无数次强调:把以前的内容学好了,就可以在不学习任何新知识 ...
- 一起学微软Power BI系列-官方文档-入门指南(7)发布与共享-终结篇+完整PDF文档
接触Power BI的时间也只有几个月,虽然花的时间不多,但通过各种渠道了解收集,谈不上精通,但对一些重要概念和细节还是有所了解.在整理官方文档的过程中,也熟悉和了解了很多概念.所以从前到后把微软官方 ...
随机推荐
- HihoCoder 1590 : 紧张的会议室(区间最大+离散化)
时间限制:20000ms 单点时限:2000ms 内存限制:256MB 描述 小Hi的公司最近员工增长迅速,同时大大小小的会议也越来越多:导致公司内的M间会议室非常紧张. 现在小Hi知道公司目前有N个 ...
- vmware 克隆linux虚拟机后的网卡修改方法
VMware虚拟机安装好CentOS6.5系统后,纯净的系统多克隆几份出来方便后期做试验.克隆步骤很简单,克隆后出现的问题是克隆后的网卡MAC地址和原系统MAC地址一样,在局域网内会有冲突,需要更改克 ...
- 点分治 [IOI2011]Race
BZOJ2599. 放板子.利用agc009-D 代码简洁了很多 #include <bits/stdc++.h> #define N 200005 using namespace std ...
- ubuntu_ root change to user
(1)从user用户切换到root用户 不管是用图形模式登录Ubuntu,还是命令行模式登录,我们会发现缺省的用户是user 但是当我们需要执行一些具有root权限的操作(如修还系统文件)时,经常需要 ...
- C#控件刷新
; ; foreach (string gen in fn_gen) { //MessageBox.Show(gen); Bitmap Bi = new Bitmap(gen); //使用打开的图片路 ...
- python 数据可视化
一.基本用法 import numpy as np import matplotlib.pyplot as plt x = np.linspace(-1,1,50) # 生成-1到1 ,平分50个点 ...
- TypeScript完全解读(26课时)_13.TypeScript完全解读-高级类型(2)
13.TypeScript完全解读-高级类型(2) 高级类型中文网的地址:https://typescript.bootcss.com/advanced-types.html 创建文件并在index. ...
- struts2注解的作用
Struts2注解 1 Struts2注解的作用 使用注解可以用来替换struts.xml配置文件!!! 2 导包 必须导入struts2-convention-plugin-2.3.15.jar包, ...
- CSS3:nth-child()伪类选择器,Table表格奇偶数行定义样式
转自爱设计 原文链接http://www.dangshopex.com/jishufenxiang/WEBkaifajishu/8653.html CSS3的强大,让人惊叹,人们在惊喜之余,又不得不为 ...
- java8--List转为Map、分组、过滤、求和等操作
利用java8新特性,可以用简洁高效的代码来实现一些数据处理~ 定义1个Apple对象: public class Apple { private Integer id; private String ...