Navigation

Wednesday 26 May 2010

How to use LINQ for SharePoint in Anonymous mode

Disclaimer - This has not been extensively tested, so please use the sample below at your own risk!
I am a really big fan of using LINQ to SharePoint. It cuts out all of the nasty CAML and you can do very complex queries with only a few lines of code. The one big let-down was not being able to use LINQ to SharePoint in Anonymous mode... or so I thought!
I actually stumbled across a blog post from Joe Unfiltered on getting LINQ to SharePoint working anonymously, so all the credit goes to him for finding this little gem.
Basically, the problem is with the SPServerDataConnection object. If you crack this baby open with Reflector then you'll see it relies on SPContext.Current for it's operations. As the objects it calls are off-limits for anonymous users it forces an authentication prompt!
There is, however, a simple way around this (a technique very similar to one I've used before when running with elevated privileges back to the Central Admin SPSite).
You basically do the following:
  1. Take backup copy of the HttpContext.Current object
  2. Set HttpContext.Current = null;
  3. Execute your LINQ to SharePoint query
  4. Restore HttpContext.Current to it’s original (backup) value
When the SPServerDataConnection object is created, as the SPContext.Current is null it will go ahead and instantiate a new object using the URL you passed through in your DataContext object :)
Important – Don’t forget to put the HttpContext.Current back again after you have finished your LINQ query. This is VERY important, otherwise things will very quickly start to break!
Sample code is below. Do Enjoy! (and thanks to Joe!)

// store a copy of our current Context's Web URL
string strWebUrl = SPContext.Current.Web.Url;

bool nullUser = false;

try
{
// check if we are running anonymously (i.e. nullUser = true)
nullUser = (SPContext.Current != null && SPContext.Current.Web.CurrentUser == null);

// take a backup copy of the HttpContext.Current object
HttpContext backupCtx = HttpContext.Current;

if (nullUser)
{
   // if running anonymously, set the current HttpContext to null
   HttpContext.Current = null;
}

// instantiate our data context (using the URL)
MyCustomDataContext thisSite = new MyCustomDataContext (strWebUrl);

// disable tracking to improve read-only performance
eventSite.ObjectTrackingEnabled = false;


// create a list of items from my custom list
EntityList<Item> listItems =
     thisSite.GetList<Item>("MyCustomList");
// query the list, only selecting items which meet the filter
var currentItems = from listItem in listItems
   where listItem.Title != ""
   select listItem;

// TODO: process your items
}
finally
{
   if (nullUser)
   {
   // don't forget to put your HttpContext back again
   HttpContext.Current = backupCtx;

   }
}
Update - Following this post (and the subsequent discussion on Twitter) there is a lot of questions around performance (creating new SPSite / SPWeb objects for each query) and the validity of nulling the HttpContext.Current in the first place.

My original statement stands .. use this at your own risk, I haven't fully tested it! :) If you do your performance benchmarking and you are happy with it then no problem!

3 comments:

  1. I just wanted to add that I ran some tests on the performance of this workaround, and it seems to do ok. http://jcapka.blogspot.com/2010/05/linq-to-sharepoint-for-anonymous-users_30.html

    ReplyDelete
  2. Is this thread safe? What if another thread happens to refer SPContext.Current while it is nulled? Maybe locking of this critical section would be in order?

    Jari

    ReplyDelete
  3. SPContext.Current runs off the HttpContext so it should be specific to your current session.

    As I said in the thread, this hasn't been extensively tested, and thread locking would be a nice addition just to be on the safe side ;)

    ReplyDelete

This blog has been moved to www.martinhatch.com

Note: only a member of this blog may post a comment.