Wednesday, December 28, 2011

Spoofing the SPContext

Recently I had to use a set of libraries developed by a client to perform some complex data retrieval and calculations. The library looked up its settings from the property bag settings using SPContext.Current.Web.  I ran into an issue because I was trying to use the library from a Project Server Event Receiver that runs in the context of a Windows Service so the SPContext object was null.  For numerous good and not so good reasons, we could not modify the libraries to take an optional SPWeb object instead of relying on the SPContext.

After doing some research, I found out that this is a common problem for unit testing libraries that relied on the SPContext.

I created the SpoofedSPContext class to allow the developer to specify what the SPContext.Current.Web object should be set as.  This class implements IDisposable so it will clean up any SharePoint objects it creates.

It has constructors that mimics all the SPSite constructors and an additional one where you can pass in the SPWeb object.  In the case when you pass in the SPWeb object, you are responsible for disposing of it.  In all other cases, the SpoofSPContext object will handle cleaning up resources.

using System;
using System.IO;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace jds.SharePoint
{
 public class SpoofedSPContext : IDisposable
 {   
  SPSite _site = null;
  SPWeb _web = null;
  HttpContext _originalContext;
  bool _disposeObjects;  
   
  public SpoofedSPContext(Guid siteUID)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUID);
   _web = _site.RootWeb;
   Init();
  }

  public SpoofedSPContext(Guid siteUID, SPUrlZone urlZone)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUID, urlZone);
   _web = _site.RootWeb;
   Init();
  }

  public SpoofedSPContext(Guid siteUID, SPUserToken userToken)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUID, userToken);
   _web = _site.RootWeb;
   Init();
  }

  public SpoofedSPContext(Guid siteUID, SPUrlZone urlZone, SPUserToken userToken)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUID, urlZone, userToken);
   _web = _site.RootWeb;
   Init();
  }
  
  public SpoofedSPContext(string siteUrl)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUrl);
   _web = _site.OpenWeb();   
   Init();
  }

  public SpoofedSPContext(string siteUrl, SPUserToken userToken)
  {
   _disposeObjects = true;
   _site = new SPSite(siteUrl, userToken);
   _web = _site.OpenWeb();
   Init();
  }

  public SpoofedSPContext(SPWeb web)
  {
   _disposeObjects = false;
   _site = null;
   _web = web;
   Init();
  }


  void Init()
  {
   // Create a new request with the URL of the SharePoint that we want to spoof.
   HttpRequest request = new HttpRequest("", _web.Url, "");
   
   // Set the browser capabilities
   request.Browser = new HttpBrowserCapabilities();

   // Store the current HTTP context so we can revert back to it.
   _originalContext = HttpContext.Current;

   // Create a new HTTP context and set it as the current context.
   HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
   
   // Set the SPWeb context item.  This is what will make SPContext.Current.Web work
   HttpContext.Current.Items[@"HttpHandlerSPWeb"] = _web;

  }

  public void Dispose()
  {
   // Based on the constructor called, this instance may or may not 
   // be responsible for displosing of the SharePoint objects.
   if (_disposeObjects)
   {
    // If a SPWeb was created, dispose of it
    if (_web != null)
    {
     _web.Dispose();
     _web = null;
    }

    // If a SPSite was created, dispose of it 
    if (_site != null)
    {
     _site.Dispose();
     _site = null;
    }
   }

   // Set the current HTTP context back to what it was.
   HttpContext.Current = _originalContext;
  }
 }
}



Now in order to use it, all you have to do is create and instance of  the SpoofedSPContext class and call your objects while it's in scope like below.

using (SpoofedSPContext ctx = new SpoofedSPContext("http://sharepointsite.com"))
{
//Any code run here will see the SharePoint site specified as the value for SPContext.Current.Web
}

No comments:

Post a Comment