If you work with Sharepoint you most probably use SPSecurity.RunWithElevatedPrivileges() method in order to perform actions under high privileged account (Sharepoint\System or account of app pool of your Sharepoint web application in IIS). Very often however inside your delegate which is passed to RunWithElevatedPrivileges() method you call Sharepoint API which may use SPContext.Current.Site or SPContext.Current.Web. These SPSite and SPWeb instances in most cases are not opened with elevated privileges and you will get famous Access denied exception. Is there any workaround for this problem? Yes it is and in this post I will show how to override SPContext.Current.Site and SPContext.Current.Web with elevated versions.
First of all let’s check implementations of SPContext.Current.Site and SPContext.Current.Web. SPContext.Current returns instance of SPContext class as you probably would expect, so let’s start with SPContext.Site:
As you can see internally it uses SPContext.Web property, so we can concentrate only on that. Let’s check its implementation:
It calls SPControl.GetContextWeb() which in turn calls SPWebEnsureSPControl() method:
Now we have all information in order to override SPContext.Current.Web. We need to store elevated instance os SPWeb in HttpContext.Items[“HttpHandlerSPWeb”] and force Sharepoint to call SPWebEnsureSPControl() method. First of all we need to set private variable m_web in SPContext site to null. As shown in listing of SPContext.Web it will call SPControl.GetContextWeb() only when it is null. After that the only thing to do is to override web site in HttpContext.Items collection with elevated version. Here is the code which makes the trick:
On lines 9,10 we store current values from HttpContext.Items collection. On line 11 we also store current value in m_web private variable in order to restore it later (this is not necessary actually – see below). Then on lines 16,17 we override values in HttpContext.Items with elevated SPSite and SPWeb. On line 18 we set SPContext.m_web to null using ReflectionHelper class (see below). After that we can call Sharepoint API which uses SPContext.Current.Web and Site and if originally we got Access denied error, now it will work successfully. In finally block we restore original values in order to minimize affected surface of our workaround. We set private m_web to original value, but we can also set it to null here – Sharepoint will retrieve original value from HttpContext.Items[“HttpHandlerSPWeb”].
Here is the code of ReflectionHelper which is used in example above (if you read my blog you may find it in other posts, but I decided to add it here as well in order to have complete example in one place):
Of course technique shown in this article should be treated as hack and you should use it only if standard ways didn’t work for you. Also I can’t predict all side effects which may be caused by this trick, however as always it is nice to have plan B if there are no other ways to proceed.