Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
With v2 of Sharepoint, restoring deleted documents was a much sought after elixir. And to that effect were made a number of attempts at coaxing the product to allow us to retain copies of the deleted items. Once such simplistic approach was documented and well presented by Maxin V Karpov and Eric Schoonover in the February 2005 issue of MSDN. The online version can be found here.
Recently me as well as a couple of my colleagues received calls from customers, seeking clarifications on how to implement the sample presented in the article. This called for a defininte reimplementation, and what an experience it was! Here are the code samples from that implementation.
I will start with the classes that will help implement the Upload and Delete Event Handlers. This is spread over two classes: BaseEventSink(which is an extended version of the file that gets downloaded from the event handler tool kit) and the EventSinkData class
/*=====================================================================
File: BaseEventSink.cs
Summary: Base class for cached event sinks.
---------------------------------------------------------------------
using System;
using System.Xml;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using Microsoft.SharePoint;
using RecycleBin.SharePoint.Configuration;
using Microsoft.SharePoint.Administration;
namespace RecycleBin.SharePoint.EventSink
{
/// <summary>
/// Base class for cached event sinks.
/// Handles impersonation and caching event sink classes to handle multiple events on
/// the same list.
/// </summary>
public class BaseEventSink : Microsoft.SharePoint.IListEventSink
{
public BaseEventSink()
{
}
/// <summary>
/// Implementation of the OnEvent method. Windows SharePoint Services will
/// call this method when an event occurs.
/// </summary>
/// <param name="listEvent">The SPListEvent object that describes the event which occured.</param>
public virtual void OnEvent(Microsoft.SharePoint.SPListEvent listEvent)
{
WriteToFile("Event has been fired, and entered the custom event handler.");
WriteToFile("List Event dump:- " + listEvent.ToString());
if (listEvent == null)
{
throw new ArgumentNullException("listEvent", "list event object cannot be null");
}
WriteToFile("Getting the cachedEventSink");
BaseEventSink sink = GetCachedEventSink(listEvent);
WriteToFile("Got the Cached Event Sink");
WriteToFile("Starting the impersonation.");
WindowsImpersonationContext wip = null;
//Ensure each sink instance handles only one event at a time.
lock(sink)
{
try
{
//Make sure the sink class has the current SPListEvent object
sink.EventInfo = listEvent;
//Impersonate the appropriate user
WriteToFile("Impersonating the administrator");
WindowsIdentity id = sink.HandlerIdentity;
if (id != null)
wip = id.Impersonate();
WriteToFile("Impersonation successful");
WriteToFile("Transferring the control to the actual event handler.");
sink.HandleEvent();
}
finally
{
//Cleanup
if (wip != null)
wip.Undo();
if (m_web != null)
m_web.Close();
//Null out cached SPWeb and SPList objects so that
//fresh ones are obtained for the next event.
m_web = null;
m_list = null;
}
}
}
/// <summary>
/// HandleEvent is called when an event occurs. Child classes should perform the main
/// event handling actions here.
/// </summary>
protected virtual void HandleEvent()
{
// if (admin.IsCurrentUserGlobalAdmin())
{
WriteToFile("Entered the EventHandler");
WriteToFile("Event fired is of type:- " + EventInfo.Type.ToString());
switch(EventInfo.Type)
{
case SPListEventType.Delete:
WriteToFile("Delete event fired.");
WriteToFile("Transferring to the Delete handler.");
DeleteHandler();
break;
case SPListEventType.Move:
// if(!Include()){break;}
WriteToFile("Move Event fired.");
WriteToFile("Transferring to the Move handler.");
MoveHandler();
break;
case SPListEventType.Update:
case SPListEventType.Insert:
case SPListEventType.Copy:
// if(!Include()){break;}
WriteToFile("Update/Insert/Copy event fired.");
WriteToFile("Transferring to the Update/Insert/Copy handler.");
UpdateInsertCopyHandler();
break;
}
}
}
/// <summary>
/// The SPListEvent object that describes the current event.
/// </summary>
protected virtual SPListEvent EventInfo
{
get
{
return m_ListEvent;
}
set
{
m_ListEvent = value;
m_web = null;
m_list = null;
m_fileUrl = null;
m_sinkData = null;
}
}
/// <summary>
/// The SPWeb object for the web site that contains the list
/// that the event occured on.
/// </summary>
protected virtual SPWeb EventWeb
{
get
{
if (m_web == null)
{
WriteToFile("Getting the web context.");
m_web = m_ListEvent.Site.OpenWeb();
}
return m_web;
}
}
/// <summary>
/// The SPList object for the list the event occured on.
/// </summary>
protected virtual SPList EventList
{
get
{
if (m_list == null)
{
m_list = EventWeb.Lists[EventInfo.ListID];
}
return m_list;
}
}
/// <summary>
/// The url of the file the event occured on.
/// Equals UrlAfter if UrlAfter is not empty. Otherwise equals
/// UrlBefore.
/// </summary>
protected string EventFileUrl
{
get
{
if (m_fileUrl == null)
{
string webUrl = m_ListEvent.WebUrl == null ? "<NULL>" : m_ListEvent.WebUrl;
string webRelUrl = m_ListEvent.UrlAfter;
if (webRelUrl == null || webRelUrl.Length == 0)
{
webRelUrl = (m_ListEvent.UrlBefore == null || m_ListEvent.UrlBefore.Length == 0 ?
"<NULL>" : m_ListEvent.UrlBefore);
}
m_fileUrl = String.Format("{0}/{1}", webUrl, webRelUrl);
}
return m_fileUrl;
}
}
protected string Data
{
get
{
if (m_sinkData == null)
{
m_sinkData = m_ListEvent.SinkData;
}
return m_sinkData;
}
}
# region Custom Code
/// <summary>
/// Converts XML string retrieved from SinkData property into an object
/// </summary>
protected EventSinkData Configuration
{
get
{
try
{
m_ConfigData = EventSinkData.GetInstance(Data);
return m_ConfigData;
}
catch(Exception ex)
{
PublishException(ex.ToString());
}
return null;
}
}//Configuration
/// <summary>
/// Makes sure that the proper folder structure is in place for copying, moving or inserting a file in to the mirror
/// document library or the Recycle Bin document library.
/// </summary>
/// <param name="web">
/// Web where the folder structure needs to be built or ensured
/// </param>
/// <param name="finalUrl">
/// Url containing the structure that needs to be built, must contain the trailing "/" if it is not a link to a file.
/// If it is a link to a file the file name will be stripped and the folder structure ensured.
/// </param>
/// <returns>
/// File path that has been built
/// </returns>
protected static string EnsureParentFolder(SPWeb web, string finalUrl)
{
finalUrl = web.GetFile(finalUrl).Url;
int x = finalUrl.LastIndexOf("/");
string epf = String.Empty;
if(x <= -1)
return epf;
epf = finalUrl.Substring(0, x);
SPFolder folder = web.GetFolder(epf);
if(folder.Exists)
return epf;
SPFolder curFolder = web.RootFolder;
string [] folders = epf.Split('/');
for(int i = 0; i < folders.Length; i ++)
{
curFolder = curFolder.SubFolders.Add(folders[i]);
}
return epf;
}//EnsureParentFolder
protected static void PublishException(string errors)
{
if(errors.Length > 0)
{
if(!EventLog.SourceExists(SOURCENAME))
EventLog.CreateEventSource(SOURCENAME, LOGNAME);
EventLog el = new EventLog();
el.Source = LOGNAME;
el.WriteEntry(errors, EventLogEntryType.Error);
}
}
# endregion
# region Windows Impersonation Code
/// <summary>
/// The WindowsIdentity class of the identity the Event Sink should impersonate.
/// </summary>
protected virtual WindowsIdentity HandlerIdentity
{
get
{
m_Identity = CreateIdentity(Configuration.UserName,
Configuration.Domain,
Configuration.Password);
return m_Identity;
}
}
/// <summary>
/// Helper method to handle creating a WindowsIdentity object from a username / domain / password.
/// </summary>
/// <param name="User">The username of the account to impersonate.</param>
/// <param name="Domain">The domain of the account to impersonate.</param>
/// <param name="Password">The password of the account to impersonate.</param>
/// <returns></returns>
public 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_CLEARTEXT = 3;
tokenHandle = IntPtr.Zero;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(User, Domain, Password,
LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new Exception("LogonUser failed with error code: " + ret);
}
//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;
}
# endregion
/// <summary>
/// Get the cached event sink object to handle this event.
/// </summary>
/// <returns>The cached IListEventSink object that should handle the current event.</returns>
private BaseEventSink GetCachedEventSink(SPListEvent evt)
{
BaseEventSink sink = null;
//Syncrhonize both reads and writes to the cache.
//Even though the Hashtable class is threadsafe for reads, we don't want to have
//multiple sink instances per list. Otherwise, if many events occur on a list
//right off the bat, you could get many instances that all have to initialize
//themselves.
lock(SinkCache.SyncRoot)
{
sink = SinkCache[evt.ListID] as BaseEventSink;
//The cached sink is only fit if it is the same type and has the same data
//as the current sink.
if (sink == null ||
! sink.GetType().Equals(this.GetType()) ||
evt.SinkData != sink.Data)
{
//Update the cache, throw away the old sink if it exists.
SinkCache[evt.ListID] = this;
sink = this;
}
}
return sink;
}
# region Win32 API calls
[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);
# endregion
protected SPListEvent m_ListEvent;
private SPWeb m_web;
private SPList m_list;
private string m_fileUrl;
private string m_sinkData;
// Custom variables start
private EventSinkData m_ConfigData;
protected static Hashtable SinkCache = new Hashtable();
private const string SOURCENAME = "Recycle Bin";
private const string LOGNAME = "Recyclebin.EventSink";
private string strMirror = "New";
private string strRecycle = "Recycle Bin";
private WindowsIdentity m_Identity = null;
private SPGlobalAdmin admin = new SPGlobalAdmin();
private WindowsIdentity id;
// private EventSinkData e = new EventSinkData();
// Custom variables end
private void UpdateInsertCopyHandler()
{
WriteToFile("Entered the UpdateInsertCopy handler.");
string fileLocation = EventInfo.UrlAfter;
WriteToFile("Got the before location:- " + fileLocation);
// string newFileLoc = String.Concat(strMirror, "/", fileLocation.Remove(0,5));
string newFileLoc = String.Concat(Configuration.MirrorLib, "/", fileLocation.Remove(0,Configuration.MainLib.Length+1));
WriteToFile("Got the new location:- " + newFileLoc);
SPFile miscFile = EventWeb.GetFile(fileLocation);
try
{
//EnsureParentFolder(EventWeb, newFileLoc);
// miscFile.CopyTo(newFileLoc, true);
// SPFolder newFolder = EventWeb.GetFolder(strMirror);
WriteToFile("Getting the folder context.");
SPFolder newFolder = EventWeb.GetFolder(Configuration.MirrorLib);
WriteToFile("Preparing to copy the file.");
Byte[] byteArray = miscFile.OpenBinary();
WriteToFile("Copying the file.");
newFolder.Files.Add(miscFile.Name,byteArray);
WriteToFile("Copy done.");
}
catch(Exception ex)
{
WriteToFile("Exception occurred:- " + ex.ToString());
PublishException(ex.ToString());
}
}//UpdateInsertCopyHandler
private void DeleteHandler()
{
WriteToFile("Entered Delete handler.");
SPFile delFile = EventWeb.GetFile(String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MainLib.Length+1)));
WriteToFile("Got the old address. " + String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MainLib.Length+1)));
string newFileLoc = String.Concat(Configuration.RecycleBinLib, delFile.Url.Remove(0,Configuration.MirrorLib.Length));
WriteToFile("New address:- " + newFileLoc);
try
{
// EnsureParentFolder(EventWeb, newFileLoc);
// delFile.MoveTo(newFileLoc, true);
WriteToFile("Getting the folder context.");
SPFolder newFolder = EventWeb.GetFolder(Configuration.RecycleBinLib);
WriteToFile("Preparing to copy.");
Byte[] byteArray = delFile.OpenBinary();
WriteToFile("Copying the file.");
newFolder.Files.Add(delFile.Name,byteArray,true);
WriteToFile("Copying complete.");
}
catch(Exception ex)
{
WriteToFile("Exception occurred:- " + ex.ToString());
PublishException(ex.ToString());
}
}//DeleteHandler
private void MoveHandler()
{
string mirrorName = strMirror;
// SPFile moveFile = EventWeb.GetFile(String.Concat(mirrorName, "/", EventInfo.UrlBefore.Remove(0,5)));
SPFile moveFile = EventWeb.GetFile(String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MirrorLib.Length-1)));
// string newFileLoc = String.Concat(mirrorName, "/", EventInfo.UrlAfter);
string newFileLoc = String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlAfter);
try
{
EnsureParentFolder(EventWeb, newFileLoc);
moveFile.MoveTo(newFileLoc, true);
}
catch(Exception ex)
{
PublishException(ex.ToString());
}
}//MoveHandler
// Logging
private static void WriteToFile(String input)
{
try
{
string filePath=@"C:\sharepoint\Log.txt";
FileInfo logFile = new FileInfo(filePath);
if(logFile.Exists)
{
if (logFile.Length >= 100000)
File.Delete(filePath);
}
FileStream fs = new FileStream(filePath,FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamWriter w = new StreamWriter(fs);
w.BaseStream.Seek(0,SeekOrigin.End);
w.WriteLine(input);
w.WriteLine("--------------------------------------------------------------");
w.Flush();
w.Close();
}
catch(System.Exception ex)
{
PublishException(ex.ToString());
}
}
}
}
Continued in the next one due to the length of the post.........
Comments
- Anonymous
July 03, 2006
It is very useful to me pls send modifications to this. - Anonymous
February 05, 2007
I was ego surfing today and came upon the following links that related to the "Add A Recycle Bin To Windows SharePoint Services For Easy Document Recovery" artcile that I contributed to last year. http://blogs.msdn.com/harsh/archive/2005/10/17/481621.aspxGreat