A common difficulty with SPSecurity.RunWithElevatedPrivileges
(most recently lamented here) is that existing SPSite
/SPWeb
objects will not work with the elevated security context. The natural workaround is to create a new SPSite
within the elevated delegate and off you go. However, Dan Larson has documented a better technique here, leveraging the SPSite
constructor that accepts an SPUserToken
. By passing the token of the system account (SHAREPOINT\system, an internal alias for the application pool identity), an elevated site is available without the complexity and complications of RunWithElevatedPrivileges
.
With the emerging best practice of separating SPSite
and SPWeb
creation from the code that uses them, I have expanded on Dan’s technique with another series of extension methods. The first step is to get the system token from an existing SPSite
, adapted from Dan’s example:
public static SPUserToken GetSystemToken(this SPSite site) { SPUserToken token = null; bool tempCADE = site.CatchAccessDeniedException; try { site.CatchAccessDeniedException = false; token = site.SystemAccount.UserToken; } catch (UnauthorizedAccessException) { SPSecurity.RunWithElevatedPrivileges(() => { using (SPSite elevSite = new SPSite(site.ID)) token = elevSite.SystemAccount.UserToken; }); } finally { site.CatchAccessDeniedException = tempCADE; } return token; }
And then we simply provide methods to operate on an elevated site created with that token:
public static void RunAsSystem(this SPSite site, Action<SPSite> action) { using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken())) action(elevSite); } public static T SelectAsSystem<T>(this SPSite site, Func<SPSite, T> selector) { using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken())) return selector(elevSite); }
Note that the object(s) returned by selector
should not be SharePoint objects that hold references to a parent site/web.
It’s also useful to provide additional methods that simplify working with elevated webs:
public static void RunAsSystem(this SPSite site, Guid webId, Action<SPWeb> action) { site.RunAsSystem(s => action(s.OpenWeb(webId))); } public static void RunAsSystem(this SPSite site, string url, Action<SPWeb> action) { site.RunAsSystem(s => action(s.OpenWeb(url))); } public static void RunAsSystem(this SPWeb web, Action<SPWeb> action) { web.Site.RunAsSystem(web.ID, action); } public static T SelectAsSystem<T>(this SPSite site, Guid webId, Func<SPWeb, T> selector) { return site.SelectAsSystem(s => selector(s.OpenWeb(webId))); } public static T SelectAsSystem<T>(this SPSite site, string url, Func<SPWeb, T> selector) { return site.SelectAsSystem(s => selector(s.OpenWeb(url))); } public static T SelectAsSystem<T>(this SPWeb web, Func<SPWeb, T> selector) { return web.Site.SelectAsSystem(web.ID, selector); }
I don’t bother to Dispose
the webs created by these calls to s.OpenWeb()
because I know s
will be disposed shortly, cleaning up the web with it.
Usage
As an example, let’s refactor Nigel’s skeleton code:
protected override void CreateChildControls() { SPWeb web = SPControl.GetContextWeb(Context); web.RunAsSystem(UpdateVisitorList); } private void UpdateVisitorList(SPWeb web) { SPList visList = web.Lists[_visitorList]; int existItemId = GetVisitorItemId(visList); web.AllowUnsafeUpdates = true; if (existItemId < 1) { SPListItem newItem = visList.Items.Add(); // Set field values newItem.Update(); } else { SPListItem oldItem = visList.GetItemById(existItemId); // Update field values oldItem.Update(); } web.AllowUnsafeUpdates = false; }
By separating the elevated operation from the logic required to elevate, the code is much easier to read and less prone to errors like leaking an elevated SPSite
.