“Stamping” PDF Files Downloaded from SharePoint 2010
First off I want to clarify that the subject of this post is not my idea as it is something that my friend Roman Kobzarev put together for his company and I merely assisted with getting the code to work. The problem that Roman was trying to solve was that his company provided numerous PDF files that registered/paying members could download and, unfortunately, they were finding some of those files being posted to various other sites without their permission; so in an attempt to at least discourage this they wanted to stamp the PDF files with some information about the user who downloaded the file.
There are various ways in which this problem can be solved but perhaps one of the simpler approaches, and the approach outlined here, was to create an HTTP Handler which intercepted requests for any PDF files and then simply retrieve the file from SharePoint, modify it, and then send it on its way. The cool thing about this approach and the pattern shown here is that it can easily be applied to any file type which requires some user or request specific modifications applied to it.
Before I get to the actual code I want to point out one third party dependency that we used: iTextSharp. This is an open source .NET library for PDF generation and manipulation. There are many options available when looking to manipulate PDF files and in this case this one was chosen due to its cost (free). So let’s get to the code.
In terms of code there really isn’t that much which is what makes this such a nice solution. The first piece is the actual implementation of the IHttpHandler interface:
using System.IO;
using System.Web;
using Microsoft.SharePoint;
using iTextSharp.text;
using iTextSharp.text.pdf; namespace Aptillon.SharePoint.PDFWatermark
{
public class PDFWatermarkHttpHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{ SPFile file = SPContext.Current.Web.GetFile(context.Request.Url.ToString());
byte[] content = file.OpenBinary();
SPUser currentUser = SPContext.Current.Web.CurrentUser; string watermark = null;
if (currentUser != null)
watermark = "This download was specially prepared for " + currentUser.Name; if (watermark != null)
{
PdfReader pdfReader = new PdfReader(content);
using (MemoryStream outputStream = new MemoryStream())
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream))
{
for (int pageIndex = 1; pageIndex <= pdfReader.NumberOfPages; pageIndex++)
{
//Rectangle class in iText represent geometric representation...
//in this case, rectangle object would contain page geometry
Rectangle pageRectangle = pdfReader.GetPageSizeWithRotation(pageIndex);
//PdfContentByte object contains graphics and text content of page returned by PdfStamper
PdfContentByte pdfData = pdfStamper.GetUnderContent(pageIndex);
//create font size for watermark
pdfData.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 8);
//create new graphics state and assign opacity
PdfGState graphicsState = new PdfGState();
graphicsState.FillOpacity = 0.4F;
//set graphics state to PdfContentByte
pdfData.SetGState(graphicsState);
//indicates start of writing of text
pdfData.BeginText();
//show text as per position and rotation
pdfData.ShowTextAligned(Element.ALIGN_CENTER, watermark, pageRectangle.Width / 4, pageRectangle.Height / 44, 0);
//call endText to invalid font set
pdfData.EndText();
}
pdfStamper.Close();
content = outputStream.ToArray();
}
}
context.Response.ContentType = "application/pdf";
context.Response.BinaryWrite(content);
context.Response.End();
} public bool IsReusable
{
get { return false; }
}
}
}
As you can see from the previous code listing, the bulk of the code involves the actual processing of the PDF file but the core SharePoint specific piece is in the beginning of the ProcessRequest() method where we use theSPContext.Current.Web.GetFile() method to retrieve the actual file requested and then, if we can get an actualSPUser object, we create a simple message that will be added to the bottom of the PDF. I’m not going to cover what’s happening with the iTextSharp objects as the point of this article is to demonstrate the pattern which can easily be applied to other file types and not how to use iTextSharp.
To deploy this class I created an empty SharePoint 2010 project using Visual Studio 2010 and added the file to the project. I then created a new Web Application scoped Feature which I use to add the appropriate web.config settings which will register the HTTP Handler. The following screenshot shows the final project structure:

Note that I also added the iTextSharp.dll to the project and added it as an additional assembly to the package by double clicking the Package.package element and then, in the package designer, click Advanced to add additional assemblies:

Before I show the code for the Web Application Feature I first want to show what settings I set for the Feature after adding it:

When you add a new Feature to a project it’s going to name it Feature1 and set the default scope to Web. The first thing I do is rename the Feature to something meaningful – in this case, because I know I’ll only have the one Feature I go ahead and name it the same as the project name: Aptillon.SharePoint.PDFWatermark (I always follow the same naming convention for my projects, which equate to WSP file names and Features: <Company>.SharePoint.<Something Appropriate for the Contained Functionality>). The next thing I do is change the Deployment Path property for the Feature so that it only uses the Feature name and does not prepend the project name; and finally I set the scope and title of the Feature. Now I’m ready to add my Feature activation event receiver.
The code that I want to include in the event receiver will handle the addition and removal of the web.config handler elements. I do this using the SPWebConfigModification class. Now there’s debate on whether this should be used or not; this is one of those classes where you might say (as my friend Spence Harbar puts it), “Just because you should use it doesn’t mean you can.” The simple explanation for this is that ideally you should be using this class to make web.config modifications but the reality is that this guy is fraught with issues and usually doesn’t work. That said, what I usually do is, where it makes sense, use this class to add and remove my entries but work under the premise that it will probably not work and plan on making these changes manually (or write a timer job which will do what this guy is attempting to do, but that’s out of scope of this article). So here’s the FeatureActivated() and FeatureDeactivating() methods that I use to add and remove the appropriate web.config entries which register the previously defined HTTP Handler:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName; SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
if (webApp == null) return; SPWebConfigModification modification = new SPWebConfigModification("add[@name=\"PDFWatermark\"]", "configuration/system.webServer/handlers");
modification.Value = string.Format("<add name=\"PDFWatermark\" verb=\"*\" path=\"*.pdf\" type=\"{0}\" preCondition=\"integratedMode\" />", asmDetails); modification.Sequence = 1;
modification.Owner = asmDetails;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
webApp.WebConfigModifications.Add(modification); webApp.Update();
webApp.WebService.ApplyWebConfigModifications();
} public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName; SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
if (webApp == null) return; List<SPWebConfigModification> configModsFound = new List<SPWebConfigModification>();
Collection<SPWebConfigModification> modsCollection = webApp.WebConfigModifications;
for (int i = 0; i < modsCollection.Count; i++)
{
if (modsCollection[i].Owner == asmDetails)
{
configModsFound.Add(modsCollection[i]);
}
} if (configModsFound.Count > 0)
{
foreach (SPWebConfigModification mod in configModsFound)
modsCollection.Remove(mod); webApp.Update();
webApp.WebService.ApplyWebConfigModifications();
}
}
With all the code in place I can deploy to my Web Application and now any time a PDF is downloaded I’ll have a nice little message displayed on the bottom of each page. Again, the intent here is to show the simplicity of the pattern and approach – with a little imagination you can easily come up with lots of other uses for this (applying security or password protection to the PDF, adding an image watermark, removing pages based on registration status thus providing “sample” versions, prefilling form fields with user data, adding a version history page, etc.). And of course all this can also be applied to other file types such as the Office files or images (though image handling would take a little more logic to ignore images not coming from document libraries).
“Stamping” PDF Files Downloaded from SharePoint 2010的更多相关文章
- How to tune SharePoint 2010 Server for better performance?
http://social.technet.microsoft.com/wiki/contents/articles/7926.sharepoint-2010-tips-for-dealing-wit ...
- How to: Add SharePoint 2010 Search Web Parts to Web Part Gallery for Upgraded Site Collections
When you upgrade to Microsoft SharePoint Server 2010, some of the new SharePoint Enterprise Search W ...
- SharePoint 2010 搜索结果没有显示部分文件
Why SharePoint 2010 search does not show some results? SharePoint 2010 search is better than ever ...
- 安装InfoPath 2013后 SharePoint 2010 出现 “找不到 Microsoft.Office.InfoPath, Version=14.0.0....” 的错误的解决方案
1. 症状 您的SharePoint 2010的服务器是不是最近一直出现这个错误呢? Could not load file or assembly 'Microsoft.Office.InfoPat ...
- Upgrade from SharePoint 2010 to SharePoint 2016
[转]http://nikcharlebois.com/upgrade-from-sharepoint-2010-to-sharepoint-2016/ In this blog, I will go ...
- SharePoint 2010 常用技巧及方法总结
1.代码调试确定进程cd c:\windows\system32\inetsrvappcmd list wppause注:保存成批处理文件,查看进程.bat,用的时候双击即可 2.类似列表新建打开方式 ...
- SharePoint 2010 文档管理之过期归档工具
前言:使用过SharePoint的人都知道,SharePoint对于操作是便捷的,但是对于数据量承载却是不令人满意的,这样,就要求我们需要更加合理的使用,规范大家的使用规则和习惯,所以,定期清理不必要 ...
- How does Web Analytics works under sharePoint 2010
[http://gokanx.wordpress.com/2013/06/15/how-does-web-analytics-works-under-sharepoint-2010/] You nee ...
- Searching External Data in SharePoint 2010 Using Business Connectivity Services
from:http://blogs.msdn.com/b/ericwhite/archive/2010/04/28/searching-external-data-in-sharepoint-2010 ...
随机推荐
- 视觉SLAM中的数学基础 第四篇 李群与李代数(2)
前言 理解李群与李代数,是理解许多SLAM中关键问题的基础.本讲我们继续介绍李群李代数的相关知识,重点放在李群李代数的微积分上,这对解决姿态估计问题具有重要意义. 回顾 为了描述三维空间里的运动,我们 ...
- 使用MyXls,出现访问被拒绝情况
MyXls 作为一款比较好用的 操作Excel 的开源项目. 最近日常偶然发现报权限的问题,导出xls失败,处理方式以下: 如报上图所示错误,则需要在web.config中 <system.we ...
- 二叉查找树(三)之 Java的实现
概要 在前面分别介绍了"二叉查找树的相关理论知识,然后给出了二叉查找树的C和C++实现版本".这一章写一写二叉查找树的Java实现版本. 目录 1. 二叉树查找树2. 二叉查找树的 ...
- Hadoop入门进阶课程6--MapReduce应用案例
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,博主为石山园,博客地址为 http://www.cnblogs.com/shishanyuan ...
- Direct3D11学习:(三)Direct3D11初始化
转载请注明出处:http://www.cnblogs.com/Ray1024 一.概述 做完一系列的准备工作之后,我们就正式进入Direct3D11的学习了.我们就从Direct3D11的初始化工作开 ...
- ASP.NET 运行时详解 揭开请求过程神秘面纱
对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就 ...
- Winform开发框架的业务对象统一调用方式
在这个纷繁的社会里面,统一性的特点能够带来很多高效的产出.牢固的记忆,这种特征无论对于企业.个人的开发工作,知识的传承都有着非常重要的作用,Winfrom框架本身就是基于这个理念而生,从统一的数据库设 ...
- javascript类的理解和使用
距离上次写博客已经过去好几个月了,现在手里的项目正好都结束了,闲暇之后开始理一下开发中一些问题,这次说一下javascript当中的类,可能很多人对于写惯了前台页面效果的coder来说,对于javas ...
- sql 添加修改说明
--为字段a2添加描述信息 EXECUTE sp_addextendedproperty N'MS_Description', '性别', N'user', N'dbo', N'table', N'表 ...
- Windows下 C++ 实现匿名管道的读写操作
由于刚弄C++没多久,部分还不熟练,最近又由于开发需求要求实现与其他程序进行通信,瞬间就感觉想到了匿名通信.于是自己查阅了一下资料,实现了一个可读可写的匿名管道: 源代码大部分都有注释: Pipe.h ...