This is quite a known issue from my previous work note, and I thought it might be a good idea to share it here.
- LINQ to SQL is good and handy to use when you query list data in your SharePoint site.
- SPSecurity.RunWithElevatedPrivileges is useful when you want to access site data under application pool service account – which normally have more resources to query.
But you need to be careful when you use them together, the users who do not have enough permission to query data may see some exceptions, this is normally hard to test as the exceptions may only be seen by users with limited permission.
SPSecurity.RunWithElevatedPrivileges(delegate ()
{
using (SPSite oSite = new SPSite(webUrl))
{
using (SPWeb oWeb = oSite.OpenWeb())
{
using (ParentDetailsRepository parentDetailsRepository = new ParentDetailsRepository(oWeb))
{
var parentDetailsItem = parentDetailsRepository.GetByEntityId(parentID);
}
}
}
});
Although you have run the above code under SPSecurity.RunWithElevatedPrivileges block, please do not expect that piece of code, which is using LINQ to SharePoint, will be able to query the ParentDetails data if the current user does not have permission to read that list.
The reason is that LINQ to SharePoint is using the below class in Microsoft.SharePoint.Linq.dll
Microsoft.SharePoint.Linq.Provider.SPServerDataConnection
which have hard-coded to use SPContext.Current by default, it will never use the SPSite and SPWeb instances created under SPSecurity.RunWithElevatedPrivileges block, this is a behaviour of LINQ to SharePoint by design.
Solution
The work around (hack) is
- backup the “SPContent.Current”;
- Set the “SPContent.Current” with the new created instance under SPSecurity.RunWithElevatedPrivileges;
- Run LINQ to SharePoint code; and
- Set the stored SPContent.Current back with the backup.
See the below code sample:
// backup HttpContext.Current
HttpContext backupCtxt = HttpContext.Current;
try
{
// if there is a SPContext make it is as null so LINQ won’t execute under current context
if (SPContext .Current != null)
HttpContext.Current = null ;
SPSecurity.RunWithElevatedPrivileges(delegate ()
{
using (SPSite oSite = new SPSite(webUrl))
{
using (SPWeb oWeb = oSite.OpenWeb())
{
// creating a new HttpContext under elevated permission and setting it as current control context web,
// because of this the code will be running under elevated permission.
HttpRequest httpRequest = new HttpRequest( "", oWeb.Url, "");
HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new System.IO.StringWriter()));
SPControl.SetContextWeb(HttpContext .Current, oWeb);
using (ParentDetailsRepository parentDetailsRepository = new ParentDetailsRepository(oWeb))
{
var parentDetailsItem = parentDetailsRepository.GetByEntityId(parentID);
}
}
}
});
}
catch (Exception ex)
{
// TODO: log exception
}
finally
{
// make sure that you are setting the actual SPContext back after your operation
if (SPContext .Current != null)
HttpContext.Current = backupCtxt;
}
Improved Solution
A better solution is to create a SPSecurity.RunWithElevatedPrivileges helper class, which will always create a new SPSite and SPWeb instance, and run the above 4 pieces of code (with comments). Whenever you need to run some codes under application pool account, use your helper rather than using the default SPSecurity.RunWithElevatedPrivileges class, as that can make your code cleaner and reduce some duplicated code.
Thanks to the author of blog http://blogs.msdn.com/b/sowmyancs/archive/2010/09/19/linq-to-sharepoint-and-runwithelevatedprivileges.aspx, so I could get the above issue quickly resolved.
I had reproduced this issue in SharePoint 2010 (cannot recall the version number due to lack of access to that environment now), and I have not run the above code in SharePoint 2013, because I’m trying to avoid using behind-code as much as possible, loving SharePoint 2013 REST API.