You most probably heard about problem with using Linq 2 Sharepoint in anonymous mode. August 2010 cumulative update fixed this problem. However recently we faced with one of variations of this problem: Linq 2 Sharepoint doesn’t work with managed metadata in anonymous mode. Below I will describe the problem.
Suppose that you have a list of products which have Applications managed metadata column which contains possible usage areas of the product (applications may be nested, i.e. term set is hierarchical). You use Linq 2 Sharepoint in order to generate business entities with strongly typed properties. What about Applications column? In won’t be mapped by default. In order to map it to list of business objects (IEnumerable<Application>) you need create POCO class Application by yourself which may look like this:
(Linq 2 Sharepoint doesn’t map entities from term store). After that you need extend mapping generated by SPMetal and add mapping from field value to collection of business object. Check the following link for more details: Extending the Object-Relational Mapping. Idea is the following: you need to create partial class (for those class which was generated by SPMetal) and implement MapFrom/MapTo and Resolve methods. I won’t explain it in details (link above contains enough information about them), just show how it may look like in our example:
Now suppose that our web application is extended to Internet zone which allows anonymous access (http://example.com and http://public.example.com). And we need to retrieve list of Products using repository pattern (which uses Linq 2 Sharepoint in actual implementation of course) and it should work both for authenticated users and anonymous users. If we would not have managed metadata it would work (as I mentioned above August 2010 CU fixed this and tests showed that it really works :) ). However in our case it won’t work for anonymous.
Let’s see how it is used:
Here we also use SharePointServiceLocator, but this is topic for other posts. When we set web and call GetAll() method it initializes Linq 2 Sharepoint data context (ProductsGeneratedCodeDataContext in this case) using URL of specified web:
When this code is executed for authenticated users, web site URL points to the default authentication zone http://example.com, for anonymous users it points to http://public.example.com. And for anonymous users it doesn’t work – list of managed metadata terms are not mapped. If we will check in debugger MapFrom() method:
we will find that item.Web points to http://public.example.com. And for TaxonomyFieldValueCollection all terms contain Label, but TermGuid property is empty (00000000-0000-0000-0000-000000000000).
The first try to fix it is to use RunWithElevatedPrivileges() and open site in default authentication zone (http://example.com). I wrote about it here: Open SPSite under RunWithElevatedPrivileges in proper zone:
After that when context is created – web URL points to http://example.com (i.e. to authenticated zone). But if we will check in debugger MapFrom() method we will see that item.Web.Url still points to http://public.example.com.
The problem is that internally Linq 2 Sharepoint uses SPContext.Current.Site and as result it points to anonymous zone. The following post contains explanation of this behavior: Linq to SharePoint: Query Across Site Collections. The root problem is in SPServerDataConnection:
In order to fix it we need to fake SPContext.Current and set it to null (as shown in code above in this case SPSite will be reopened). After code will be executed – we need to restore SPContext.Current. I.e. this is the same solution which was used for general Linq 2 Sharepoint in anonymous mode problem. Nice wrapper is shown here: Making Linq to SharePoint work for Anonymous users:
So the final fix will look like this:
Note that we can’t use SPContext.Current inside AnonymousContextSwitch because it will be null.
After this in MapFrom() method item.Web will point to http://example.com and TermGuid won’t be empty for all terms and will be mapped successfully. From my vision this problem looks like a bug in Sharepoint.