LINQ to SharePoint – Obscure Workarounds

October 1 2010 206 comments

Previously I wrote about querying with SPLinq and the scope being the current site collection when running code in SharePoint. I’ve also written about using Ajax panel in web parts editor part (this post is slightly edited to serve this one). I’m going to take these two posts as basis for this post where I will speculate with workarounds available on the web to go round the two limitations of SPLinq:

  • Scope is current site collection
  • Anonymous use is not supported

Let’s make it clear at this point that I’ve not discovered the techniques to conquer SPLinq. The credit goes to these quys who have exposed the information to the web: accessing data cross site collections with SPLinq, making Linq to SharePoint work for Anonymous users, Linq to SharePoint presentation among others. Having given credits to those who deserve it, I have to also mention that I’m not too keen on the techniques and tricks. Clever they still are and I believe the only solutions to the problems. To go with what we have was my starting point this time to make some kind of a POC.

I tried to make a generalization, not being as successfull as I would have hoped to be to wrap anonymous support and cross site collection queries available to SharePoint context. This is what I came up with, a disposable object with one object to manipulate, HttpContext.Current and another object to store for the time of my disposable object’s life cycle, SPContext.Current.Web.CurrentUser:

public class ContextSwitch : IDisposable
{
    private readonly HttpContext _currentContext;
    private readonly SPUser _currentUser;
    private BlogDataContext _dataContext;
    public bool IsAnonymous
    {
        get
        {
            return _currentUser == null;
        }
    }

    public ContextSwitch()
    {
        _currentContext = HttpContext.Current;
        _currentUser = SPContext.Current.Web.CurrentUser;
        HttpContext.Current = null;
    }

    public BlogDataContext GetContext(string url)
    {
        if (IsAnonymous)
        {
            SPSecurity.RunWithElevatedPrivileges(
                    delegate { _dataContext = new BlogDataContext(url); }
                );
        }
        else
        {
            _dataContext = new BlogDataContext(url);
        }
        return _dataContext;
    }

    public void RunWithElevatedPrivileges(SPSecurity.CodeToRunElevated
        secureCode)
    {
        if (IsAnonymous)
        {
            SPSecurity.RunWithElevatedPrivileges(secureCode);
        }
    }

    public void Dispose()
    {
        _dataContext = null;
        HttpContext.Current = _currentContext;
    }
}

The idea of the above clip is to go around the condition in Microsoft.SharePoint.Linq.Provider.SPServerDataConnection by setting current httpcontext null in the constructor and setting it back to what it was in the disposer. The DataContext is also exposed via elevated privileges when used in anonymous context.

Second phase was to create the data access model with SPMetal similarly to what was described in my previous post and to extend it with custom mapping to have one more field in the queried SharePoint blog posts, the publication level of the post item.

public partial class Post : ICustomMapping
{
    public SPFileLevel PublicationLevel { get; private set; }

    [CustomMapping(Columns = new String[] { "*" })]
    public void MapFrom(object listItem)
    {
        var item = (SPListItem)listItem;
        PublicationLevel = item.Level;
    }

    public void MapTo(object listItem){}

    public void Resolve(RefreshMode mode,
        object originalListItem, object databaseListItem){}
}

The third phase would be to consume the wrapper or whatever you want to call it from a querying interface. To demonstrate, I built a similar Blog-querying model, which was also the part of the console application demonstration in my previous post:

public class BlogsDataAccess
{
    public static IEnumerable<Post> GetFromWebs(int limit,
        List<string> sources)
    {
        var posts = new List<Post>();
        if (sources == null) return posts;

        foreach (var source in sources)
        {
            var siteUrl = source.Split(';')[1];
            var webRelativeUrl = source.Split(';')[2];
            using (var site = new SPSite(siteUrl))
            {
                using (var web = site.OpenWeb(webRelativeUrl))
                {
                    if (!web.Exists) continue;
                    posts.AddRange(GetFromWeb(limit, web.Url));
                }
            }
        }
        return posts.OrderByDescending(p => p.Published).Take(limit);
    }

    public static List<Post> GetFromWeb(int limit, string webUrl)
    {
        using (var contextSwitch = new ContextSwitch())
        {
            var posts = new List<Post>();
            if (contextSwitch.IsAnonymous)
            {
                contextSwitch.RunWithElevatedPrivileges(
                    delegate
                    {
                        posts =
                        (from post in
                                contextSwitch.GetContext(webUrl).Posts
                            where post.PublicationLevel ==
                            SPFileLevel.Published
                            orderby post.Published descending
                            select post).Take(limit).ToList();
                    });
            }
            else
            {
                posts = (from post in
                                contextSwitch.GetContext(webUrl).Posts
                            where post.PublicationLevel ==
                            SPFileLevel.Published
                            orderby post.Published descending
                            select post).Take(limit).ToList();
            }
            return posts;
        }
    }
}

There is a clear problem in the clip above: running with elevated privileges. Although the query only gets published items, how can we be sure the queried sites allow anonymous access – that would be another thing to consider. My first intention was only to get the DataContext with elevated privileges but when testing, I noticed I couldn’t get the anonymous scenario to work with Blog-sites in another site collection exposed to anonymous access (403 Forbidden) whereas there were no problems retrieving posts from the current site collection – that is why the query is also executed with elevated privileges in anonymous use, not only the creation of the BlogDataContext via ContextSwitch.GetContext. Please tell me, if you find the reason for this.

Fourth phase was to include Ajax panel driven web part described in my earlier blog post to the solution to being able to test different kinds of scenarios fluently. So I added this clip to the web part’s code:

protected override void CreateChildControls()
{
    base.CreateChildControls();
    var posts = BlogsDataAccess.GetFromWebs(Limit, Webs);

    foreach (var post in posts)
    {
        Controls.Add(new LiteralControl(post.Title + "<br/><br/>"));
    }
}

And yes, it works – the web part lists blog posts from two different site collections even in anonymous use and the web part’s custom properties also work nicely with the scenario.

Am I happy to have accomplished this and would I use the solution to show information of blog posts in some public SharePoint site. Absolutely not. We have our built in content query web part – let’s stick to that. I might consider using the solution to overcome the site collection limitation if absolutely needed in a scenario where anonymous use is prohibited but I think I would feel a little nauseous.

I still haven’t overcome the explanations – or lack of them – given for the SPLinq’s scope being the current site collection. It can’t be that site colletcion should be the scope of custom queries. We’ve had our CrossListQueryCaches, SPSiteDataQueries, the search API along for some time now and with these libraries and techniques it hasn’t been a hard task to query data from another site collection. Might it have something to do with the fact that the SPLinq is a two way interface. You can also submit and delete data to and from lists with it and if some vulnerability would be exposed if there wasn’t the oddity – and oddity it is – in SPServerDataConnection’s constructor which forces the use of SPContext.Current.Site in SharePoint context, I could not say.

Popularity: 13% [?]

206 comments to “LINQ to SharePoint – Obscure Workarounds”

  1. We Also Share Some Information About Our Business

  2. site says:

    I think that is one of the most vital information for me.
    And i’m glad studying your article. But want to statement on some common issues, The site style is wonderful, the
    articles is in point of fact nice : D. Just right process,
    cheers

  3. web site says:

    Informative article, exactly what I needed.
    web site parimatch rates

  4. I like the valuable info you supply in your articles.
    I will bookmark your blog and take a look at again right
    here frequently. I’m quite sure I will be informed
    many new stuff proper right here! Good luck
    for the next!

  5. boxchiase says:

    Wow! After all I got a webpage from where I be
    capable of really get useful data concerning
    my study and knowledge.

  6. Hmm it seems like your website ate my first comment
    (it was super long) so I guess I’ll just sum it up what I wrote and
    say, I’m thoroughly enjoying your blog. I too am an aspiring blog blogger but
    I’m still new to the whole thing. Do you have any
    recommendations for rookie blog writers? I’d definitely appreciate it.

Leave a Reply