Does somebody of you guys from Europe remembers the obscure TV show called “Games without the borders“, aired at the end of 1970s – beginning of 1980s in most European countries. There were bunch of people, from different European nations, dressed in the obscure costumes, doing some ridiculous things just to get some points. I was a kid back then – 6 or 7 years old – but I remember the show very well. It was a family event.
And I remember it every single time I need to work with the SPContext object. Remember: doing ridiculous things in the obscure environment? Exactly.
So, let’s talk about the SPContext.
It is actually nothing else but a derivate of a well known HttpContext. Microsoft words: “Represents the context of an HTTP request in Microsoft SharePoint Foundation“. For the most of the times, you just use it in the SharePoint front end, to know where exactly you are – to retrieve the current SPSite, SPWeb, SPList, SPListItem… whatsoever object, which is currently of interest to you.
The Problem
Well, there we see already a few tricks and drawbacks. SharePoint front end? Yes, SharePoint front end. We know our SPContext in our web parts and application pages.
But, we don’t want to talk to our SharePoint Data from our web parts. We just don’t want. It’s not a nice thing to do, and we don’t do that. Our data access layer needs a SPContext, not our front end.
Since it takes some time to initialize the data we have in SPContext – waking up a SPWeb could take some seconds – it would be nice thing if we could pass our SPContext from the front end, to the SharePoint data access layer. We can, nothing stops us to do that – we will see in one of the next articles of this architecture series how to do that – but that is actually not an issue. That is straight forward.
You remember the last article in the Sharedove SharePoint architecture series, the one which where we described creating a Windows 8 Metro Client that consumes SharePoint data. Well, we don’t have a SPContext there. As we have said, we have it only in the SharePoint front end components.
If we look at this schema, we clearly see two paths from which the SharePoint data are accessed: one is through the SharePoint UI (front end) layer – this is where we have SPContext, and the other one is through the Service Layer, which is used by all other front ends for our data. We don’t have our context there. The orange marked components and arrows.
Well, we want to use the same SharePoint data access layer for both cases – it would be a waste of time to write a data logic twice. This means, we need to create our own context objects – SPSite and SPWeb.
But, there is another issue here, which need to discussed, even before we created a SPWeb: how to get rid of it. Yes, we need to dispose SPWeb and SPSite objects manually. .NET will not do it for us. But – not if we use the SPWeb which we have received from the SharePoint UI (web parts). If we dispose that one, we have just put our SharePoint to sleep. So we need to differentiate: if we come from SharePoint UI – no disposing. If we come from any other place – we must dispose it. This does not make the things easy, does it.
The solution
Let’s look at our SharePoint Data Access Layer project (the code can be downloaded from the Sharedove Architecture Project CodePlex site). We will create a ContextHelper class, which will help us in handling all the tasks related to the SPContext issues.
In each public method in out data layer class, we will first create a ContextHelper class, and pass a context object to it – ContextHelper constructor requires some context object to be passed. The context object will be of a type “object”, but we will actually expect a SPContext object (if coming from the SharePoint UI), or a string object (if we are creating the object manually).
If we pass a string, we will establish a connection that it will be in format {site};{web}, means, site and web separated by a semicolon. That will be enough info for us to create a SPSite and SPWeb
In the ContextHelper constructor itself, we will determine which type of object did we pass. If it was a SPContext, we will just assign SPSite and SPWeb objects from the context to the member variables, which are then exposed as public properties from the ContextHelper objects.
ContextTypes enumeration is used to remember what context did we use.
/// <summary> /// Initialize the Context Helper class - check the type of the passed context, and, if necessary, instantiate SPWeb /// </summary> /// <param name="context">Context object which we have received</param> public ContextHelper(object context) { m_ContextType = ContextTypes.Unknown; if (context is SPWeb) { m_ContextType = ContextTypes.Spcontext; m_Web = context as SPWeb; m_Site = m_Web.Site; } else if (context is string) { m_ContextType = ContextTypes.Initialize; CreateContext(context as string); } }
But, if we have passed the string as a context object, we need to create our SPSite and SPWeb ourselves:
/// <summary> /// Initialize the Context Helper class - check the type of the passed context, and, if necessary, instantiate SPWeb /// </summary> /// <param name="context">Context object which we have received</param> public ContextHelper(object context) { m_ContextType = ContextTypes.Unknown; if (context is SPWeb) { m_ContextType = ContextTypes.Spcontext; m_Web = context as SPWeb; m_Site = m_Web.Site; } else if (context is string) { m_ContextType = ContextTypes.Initialize; CreateContext(context as string); } }
We notice here, that we are creating SPSite and SPWeb without using the “using” block, which would do the SPSite/SPweb disposal for us. The reason is very simple – we need our SPSite and SPWeb, we must use it in our data access layer methods. And get rid of it afterwards. This means that we need a DisposeContext method in our ContextHelper class, which will be called at the end of the each Data Access Layer method, to dispose SPSite and SPWeb, if they need to be disposed:
/// <summary> /// Dispose context /// </summary> internal void DisposeContext() { // //Dispose the context if it was initialized manually (not passed from the Front End) if (m_ContextType == ContextTypes.Initialize) { try { // //dispose web and site if (m_Web != null) m_Web.Dispose(); if (m_Site != null) m_Site.Dispose(); } catch (Exception ex) { throw new ApplicationException("Could not dispose the context", ex); } } }
And then – what if an exception has been thrown between SPSite/SPWeb creation through the ContextHelper class, and SPSIte/SPWeb disposal through the DisposeContext method? Does this mean that these object would remain in memory, consuming it without any need? This is why we will create a ContextHelper destructor method, which will be created always when our ContextHelper class is disposed, where we ensure that out SPSite and SPWeb objects are non existing when at the moment.
/// <summary> /// When disposing ContextHelper, look in the destructor if the SPWeb is disposed - if it should be disposed at all. /// Dispose it if it isn't /// </summary> ~ContextHelper() { DisposeContext(); }
The legitimate question would be – why do we need to call the DisposeContext method manually then, from our methods, when the SPSite/SPWeb will be disposed anyway through the destructor – well, because it is a nice thing to do. It makes us aware of what is actually going on there.
Last, but not the least – we started speaking about SPContext, and ended up creating SPSite and SPWeb. Well, these are two most common objects you actually need from the SPContext, which cover 95% of the needs, but if you really need a whole SPContext object created, just remember that it is a derivate of HttpContext. And since we already have a SPWeb object created above, it is not a difficult task to derive the SPContext:
/// <summary> /// Sets the current SPContext, based on the SPWeb value, if not present. Since the SPContext is based on the HttpContext, /// it is current Http Context that we will actually be setting /// </summary> private void setCurrentSpContext() { // // Ensure HttpContext.Current if (HttpContext.Current == null) { HttpRequest request = new HttpRequest("", m_Web.Url, ""); HttpContext.Current = new HttpContext(request, new HttpResponse(TextWriter.Null)); } // // SPContext is based on SPControl.GetContextWeb() if (HttpContext.Current.Items["HttpHandlerSPWeb"] == null) { HttpContext.Current.Items["HttpHandlerSPWeb"] = m_Web; } }
Well, this makes the whole story working, both for web parts, Windows 8 Metro clients. Another myth about SharePoint being “different“, and “needs to access the data from the UI” broken. There is a data access layer, implemented in a clean way, which enables us to access the SharePoint data from wherever we come, exactly as we have wanted it.
The whole source code, as always, at http://sharedove.codeplex.com