Walkthrough: Handling Document Library Events
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
Windows SharePoint Services provides document library events that enable developers to build upon the SharePoint platform. Developers can create managed code assemblies that define handlers for these events and then bind the handlers to document libraries. The event handlers can call into the SharePoint object model to access the configuration and content databases directly, or they can invoke external services, using Windows SharePoint Services as a user interface for other systems.
Note
This walkthrough describes a procedure that is maintained for backward compatibility with Windows SharePoint Services 2.0 event handling.
Prerequisites
The following table describes the events for document libraries that Windows SharePoint Services provides.
Event |
Description |
---|---|
Undo Check Out |
Changes made to a checked-out document are undone. |
Check In |
A document is checked in to the library. |
Check Out |
A document is checked out from the library. |
Copy |
A document in the library is copied. |
Delete |
A document is deleted from the library. |
Insert |
A new document is saved to the library. |
Move or Rename |
A document is moved or renamed. |
Update |
An existing document or the value of a custom column in the library is edited. |
In the context of Windows SharePoint Services, an event handler is a Microsoft .NET class that implements the IListEventSink interface, whose one method, OnEvent, is used within the handler. The SPListEvent object contains information about an event that occurs, and you can return the type of event through the Type property. Use the Site property to access the object model of the Microsoft.SharePoint namespace within the handler.
You must install the managed assembly that defines an event handler in the global assembly cache. In a server farm configuration, you must install this managed assembly in the global assembly cache of each front-end Web server.
To deploy an event handler on a server, event handling must be enabled on the Virtual Server General Settings page in SharePoint Central Administration.
Only users with full permissions for a document library can add event handlers.
Event Settings
The list metadata for a document library binds the class of an event handler to a library by means of the following properties. The metadata can be set on the Document Library Advanced Settings page for the document library through code that implements the EventSinkAssembly, EventSinkClass, and EventSinkData properties of the SPList class, or through definitions contained in front-end site templates. The following table summarizes the event settings.
Setting |
Type |
Description |
---|---|---|
Assembly Name |
String |
The strong name of the event handler assembly file that lives in the global assembly cache. |
Class Name |
String |
The fully qualified, case-sensitive name of the class within the assembly. |
Properties |
String |
An arbitrary string of custom properties for use by the event handler. The length of the string cannot exceed 255 characters. |
After you install the event handler assembly, on the Document Library Advanced Settings page for the document library, specify the strong name of the assembly in the Assembly Name box. The strong name must be in the format Assembly_Name, Version=Version, Culture=Culture, PublicKeyToken=Public_Key_Token. You can obtain these values by browsing to %windir%\assembly in Windows Explorer. As an example, the strong name for the Microsoft.SharePoint assembly resembles Microsoft.SharePoint, Version=12.0.0.0, Culture=Neutral, PublicKeyToken=71e9bce111e9429c.
The value that you specify in the Class Name box must be the full, case-sensitive name of a class defined in the specified assembly that implements the IListEventSink interface. For example, the full name of the class in the following example would be myNamespace.myClass.
Namespace myNamespace
Public Class myClass
Inherits Microsoft.SharePoint.IListEventSink
Public Sub OnEvent(evt As Microsoft.SharePoint.SPListEvent)
'Add an announcement
AddAnnouncement(evt)
End Sub 'OnEvent
Public Sub AddAnnouncement(evt As
Microsoft.SharePoint.SPListEvent)
.
.
.
End Sub 'AddAnnouncement
End Class 'myClass
End Namespace 'myNamespace
namespace myNamespace
{
public class myClass : Microsoft.SharePoint.IListEventSink
{
public void OnEvent(Microsoft.SharePoint.SPListEvent evt)
{
//Add an announcement
AddAnnouncement(evt);
}
public void AddAnnouncement
(Microsoft.SharePoint.SPListEvent evt)
{
.
.
.
}
}
}
Typing a value in the Properties box is optional. This value can contain up to 255 characters for use by the event handler.
Events in Relation to Other Events
An event handler running in the context of Windows SharePoint Services cannot generate other events. To program side effects for an event, you must provide the code within the event handler. The event handler is called asynchronously from the actual request.
A new instance of an event handler class is created for each event. Consequently, you cannot store state in your event handler class and expect it to persist across multiple events. You can, however, create a base class for an event handler or for multiple event handlers that caches state so that the same sink object can be used for all events on a document library.
Caching Credentials
Event handlers run in the context of the Internet Information Services (IIS) Application Pool Identity, an account that typically has no permissions in Windows SharePoint Services. If your handler needs to interact with Windows SharePoint Services through the object model, you must establish and cache credentials through impersonation of a user. Accessing the object model in an event handler without impersonation is not supported. For information, see Impersonating and Reverting in the .NET Framework Developer's Guide on MSDN.
Security Context
The event handler thread runs within the same application pool account as the SharePoint Web application. If, however, the event handler needs to run in a different account to manipulate Windows SharePoint Services or an external service, the developer must maintain his or her own user credentials and manage his or her own security context.
Exceptions and Errors
Exceptions for event handlers used in Windows SharePoint Services are thrown in the following conditions, for which a Microsoft Windows NT event log entry is created:
The assembly for the handler cannot be loaded.
The class defining the handler is not found.
The class does not implement the IlistEventSink interface.
The OnEvent method throws an exception.
Note
When you restart IIS, each IIS worker process terminates only after all its queued work items, including HTTP requests and document library events, complete their tasks.
Definitions and Templates
Developers can define event handler assemblies within list definitions, which provides a useful means for deploying solutions such as workflow-enabled document libraries in which events are already bound to the appropriate handlers. An event handler can be bound to a document library through the EventSinkAssembly, EventSinkClass, and EventSinkData attributes of the List element in the Schema.xml file for the list type. When an end user creates a library through the user interface (UI) from a list definition that specifies an event handler, list event settings are automatically set to the values specified in the definition. When an end user saves a document library as a list template, event settings specified through the UI are preserved in the template.
For information about definitions and templates for sites or lists, see Working with Site Templates and Definitions.
Event handler example
The following programming task creates an event handler that deletes the oldest version of a file in a document library when the number of versions for the file reaches a specified number. To establish credentials for the handler, a method returns a token to the event handler for impersonation of a user.
Procedure
To create a document library event handler
On the File menu in Visual Studio 2005, point to New and then click Project.
In the New Project dialog box, select Visual Basic Projects or Visual C# Projects in the Project Types box.
In the Templates box, select Class Library.
In the Name box, type the name of the project, which in this task is "DocLibHandler".
In the Location box, type the path for the project, and then click OK.
In Solution Explorer, right-click the References node, and click Add Reference on the shortcut menu.
On the .NET tab of the Add Reference dialog box, select Windows SharePoint Services in the list of components, click Select, and then click OK.
Add the following directives to the Class1.cs or Class1.vb file of the DocLibHandler project after the System directive that is included by default.
Imports System.Security.Principal Imports System.Runtime.InteropServices Imports Microsoft.SharePoint
using System.Security.Principal; using System.Runtime.InteropServices; using Microsoft.SharePoint;
Change the namespace and class name in the Class1.cs or Class1.vb file to be DocLibEventHandler and DocVersionHandler, as follows.
Namespace DocLibEventHandler Public Class DocVersionHandler Implements IlistEventSink
namespace DocLibEventHandler { public class DocVersionHandler : IListEventSink {
Add the OnEvent method of the IListEventSink interface to the DocVersionHandler class, as follows.
Private maxVersions As Integer = 5 Sub IListEventSink.OnEvent(listEvent As Microsoft.SharePoint.SPListEvent) ' TODO: ***** PROVIDE YOUR IMPLEMENTATION HERE **** ' Get the credentials (User_Alias, Domain, Password) that this ' event sink instance should run as and initialize the wic ' object by calling the CreateIdentity method Dim wic As WindowsImpersonationContext = CreateIdentity(User_Alias, Domain, Password).Impersonate() If listEvent.Type = SPListEventType.Update OrElse listEvent.Type = SPListEventType.CheckIn Then Dim site As SPWeb = listEvent.Site.OpenWeb() Dim file As SPFile = site.GetFile(listEvent.UrlAfter) Dim nDocVersions As Integer = file.Versions.Count While maxVersions > 0 AndAlso nDocVersions > maxVersions - 1 file.Versions.Delete(0) nDocVersions = file.Versions.Count End While End If wic.Undo() End Sub 'IListEventSink.OnEvent
private int maxVersions = 5; void IListEventSink.OnEvent (Microsoft.SharePoint.SPListEvent listEvent) { /* TODO: ***** PROVIDE YOUR IMPLEMENTATION HERE **** Get the credentials (User_Alias, Domain, Password) that this event sink instance should run as and initialize the wic object by calling the CreateIdentity method*/ WindowsImpersonationContext wic = CreateIdentity(User_Alias, Domain, Password).Impersonate(); if ((listEvent.Type == SPListEventType.Update) || (listEvent.Type == SPListEventType.CheckIn)) { SPWeb site = listEvent.Site.OpenWeb(); SPFile file = site.GetFile(listEvent.UrlAfter); int nDocVersions = file.Versions.Count; while ((maxVersions > 0) && (nDocVersions > (maxVersions - 1))) { file.Versions.Delete(0); nDocVersions = file.Versions.Count; } } wic.Undo(); }
The OnEvent method calls the following method to impersonate an account with administrative permissions.
Important
Do not pass credentials in clear-text, because doing so presents a security risk. To impersonate a user more securely, you can return the information programmatically with functions provided by the operating system for getting secret data from the user, such as the CryptProtectData and CryptUnprotectData functions of the Data Protection API (DPAPI).
Include the following method within the DocVersionHandler class.
Protected Shared Function CreateIdentity(User As String, Domain As String, Password As String) As WindowsIdentity ' The Windows NT user token. Dim tokenHandle As New IntPtr(0) Const LOGON32_PROVIDER_DEFAULT As Integer = 0 Const LOGON32_LOGON_NETWORK As Integer = 3 tokenHandle = IntPtr.Zero ' Call LogonUser to obtain a handle to an access token. Dim returnValue As Boolean = LogonUser(User, Domain, Password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, tokenHandle) If False = returnValue Then Dim ret As Integer = Marshal.GetLastWin32Error() Throw New Exception("LogonUser failed with error code: " + ret) End If System.Diagnostics.Debug.WriteLine(("Created user token: " + tokenHandle)) 'The WindowsIdentity class makes a new copy of the token. 'It also handles calling CloseHandle for the copy. Dim id As New WindowsIdentity(tokenHandle) CloseHandle(tokenHandle) Return id End Function 'CreateIdentity <DllImport("advapi32.dll", SetLastError := True)> _ Private Shared Function LogonUser(lpszUsername As String, lpszDomain As String, _ lpszPassword As String, dwLogonType As Integer, dwLogonProvider As Integer, _ ByRef phToken As IntPtr) As Boolean <DllImport("kernel32.dll", CharSet := CharSet.Auto)> _ Private Shared Declare Auto Function CloseHandle Lib "kernel32.dll" (handle As IntPtr) As Boolean
protected static WindowsIdentity CreateIdentity(string User, string Domain, string Password) { // The Windows NT user token. IntPtr tokenHandle = new IntPtr(0); const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_NETWORK = 3; tokenHandle = IntPtr.Zero; // Call LogonUser to obtain a handle to an access token. bool returnValue = LogonUser(User, Domain, Password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, ref tokenHandle); if (false == returnValue) { int ret = Marshal.GetLastWin32Error(); throw new Exception("LogonUser failed with error code: " + ret); } System.Diagnostics.Debug.WriteLine("Created user token: " + tokenHandle); //The WindowsIdentity class makes a new copy of the token. //It also handles calling CloseHandle for the copy. WindowsIdentity id = new WindowsIdentity(tokenHandle); CloseHandle(tokenHandle); return id; } [DllImport("advapi32.dll", SetLastError=true)] private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet=CharSet.Auto)] private extern static bool CloseHandle(IntPtr handle);
To create a strong name for the assembly, go to the Local_Drive:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin directory (Local_Drive:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin in Visual Studio .NET Version 7) through a command prompt, and type sn -k Local_Drive:\DocLibHandler.snk, which creates a key file for the assembly.
Add the following attributes for the assembly version and key file to the AssemblyInfo file of the project, replacing the attributes that are provided by default.
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyKeyFile("Local_Drive:\\DocLibHandler.snk")]
Click Build Solution on the Build menu.
To install your solution in the global assembly cache, drag the dynamic-link library (DLL) of your assembly to the Local_Drive:\WINDOWS\assembly directory in Windows Explorer.
On the Virtual Server General Settings page in SharePoint Central Administration, select On in the Event Handlers section to enable use of event handlers on the virtual server.
From a view of the document library to which you want to attach the event handler assembly, click Modify settings and columns, and then click Change advanced settings on the page for customizing the document library.
On the Document Library Advanced Settings page, type the strong name for the assembly in the Assembly Name box. (For example, DocLibHandler, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=0fafac2a0cc92888.) You can get the values used in the strong name by right-clicking the DLL of your assembly in the Local_Drive:\WINDOWS\assembly directory and then clicking Properties on the shortcut menu.
Type the fully qualified, case-sensitive name of the class in the Class Name box, which in this example is DocLibEventHandler.DocVersionHandler.
Reset IIS by typing iisreset at a command prompt.
Next Steps
To implement the handler in a document library, versioning must be enabled on the Document Library Settings page for the library.