Earlier today I ran into the issue of Linq to SharePoint not working for anonymous users. The issue is discussed at a number of places:
http://blog.mastykarz.nl/sharepoint-2010-linq-doesnt-support-anonymous-users/http://social.technet.microsoft.com/Forums/en-US/sharepoint2010programming/thread/9b59abcb-6bce-42f1-9eae-ad9561753044Seeing as 95% of the SharePoint work I do is on public facing web sites, this was a real disappoinment for me. SharePoint 2010 is supposed to be much more useable internet sites - so this was a bit of a shock. Linq to SharePoint was one of those features we all looked forward to!
Before going back to CAML queries, I thought I would have a look at what potential workarounds or even hacks I could come up with to get this working.
My first try was to just wrap my code in the usual SPSecurity.RunWithElevatedPrivileges method. That didn't work. A number of experiments later (and then finding this
post), I arrived at this picture form Reflector:

The class we are looking at here is responsible for creating the data connection for the Microsoft.SharePoint.Linq.DataContext class. The important thing to notice here is the highlighted line. If the SPContext.Current is not null, the code uses the SPSite object from the current SPContext.
As documented all over the web, the RunWithElevatedPrivileges method will be of no help if you do not create new SP* objects. See
http://msdn.microsoft.com/en-us/library/bb466220.aspx "...You cannot use the objects available through the Microsoft.SharePoint.SPContext.Current property. That is because those objects were created in the security context of the current user..."
So since the code shown in reflector does exactly what the above mentioned article warns against, browsing your site as an anonymous user eventually causes a login prompt on your site since the current SPContext represents that anonymous user who rightly should NOT be able to run Linq queries against your database.
Next I was inspired by this
post and thought: What if I can somehow mess with the SPContext.Current object? If I could get it to be null, I could force that code in the SPServerDataConnection class to create a new SPSite object!
The SPContext.Current object is read only. However, it derives in one way or another from the HttpContext.Current, and that is writeable. So my next attempt was to check if my user was an anonymous user, and if so, set the HttpContext to null. Short story - it worked!
After some cleanup, I created the following helper method:
public static class AnonymousContextSwitch
{
public static void RunWithElevatedPrivelligesAndContextSwitch(SPSecurity.CodeToRunElevated secureCode)
{
try
{
//If there is a SPContext.Current object and there is no known user, we need to take action
bool nullUser = (SPContext.Current != null && SPContext.Current.Web.CurrentUser == null);
HttpContext backupCtx = HttpContext.Current;
if (nullUser)
HttpContext.Current = null;
SPSecurity.RunWithElevatedPrivileges(secureCode);
if (nullUser)
HttpContext.Current = backupCtx;
}
catch (Exception ex)
{
string errorMessage = "Error running code in null http context";
//Use your favourite form of logging to log the error message and exception ....
}
}
}
The logic is as follows:
- Check if the situation requires action: Is the user anonymous?
- Backup the current HttpContext
- Set the current HttpContext to null - thus forcing the creation of new SP* objects
- Use The RunWithElevatedPrivileges method to execute code specified by the caller. Note that I reuse the SPSecurity.CodeToRunElevated delegate to mimic the RunWithElevatedPrivileges method.
- Set the current HttpContext to the backed up object
Calling this function is identical to the way RunWithElevatedPrivileges is called:
string currentWebUrl = SPContext.Current.Web.Url;
AnonymousContextSwitch.RunWithElevatedPrivelligesAndContextSwitch(
delegate
{
MyDataContext dctx = new MyDataContext(currentWebUrl);
//... your code...
});
Just remember that the SPContext.Current object should NOT be referenced inside the delegate code. This will throw all sorts of Null Reference exceptions. If you need data such as the current web url, throw it in a string variable before you switch the context. See the example above.
I have tested this code for retrieving as well as updating data in a list and it worked great. I have also tested this for a logged in as well as an anonymous user and it seems to work for both.
Lastly, I need to say that I just figured this out today and I have NO idea what the long term impact of this will be, or if it will work as expected in all cases. Use at your own risk and all that. Happy coding!