I was reviewing some code today and came across a perfect case study for Dependency Injection. It also required another peak under the hood of Microsoft.Office.Server.ServerContext, the results of which I thought might be worth sharing.
From MSDN, ServerContext
“provides run-time methods for shared services in Microsoft Office SharePoint Server 2007.” If you’re not familiar with Shared Service Providers, Shane Young has a concise overview or check out Ted Pattison‘s developer overview on MSDN. The SSP APIs span several namespaces:
- Microsoft.Office.Excel.Server
- Microsoft.Office.Server.ApplicationRegistry (Business Data Catalog)
- Microsoft.Office.Server.Audience
- Microsoft.Office.Server.Search
- Microsoft.Office.Server.UserProfiles (includes My Sites)
The entry point to all of these APIs is ultimately a ServerContext
object, made available through several static methods and properties:
- ServerContext.Default
Returns a new instance for the local farm’s default provider. - ServerContext.Current
ReturnsServerContext.GetContext(HttpContext.Current)
. - ServerContext.GetContext(HttpContext httpContext)
Returns a shared ServerContext instance for the given request context. The provider is determined first through the microsoft.office.server/sharedService section in Web.config, and from the contextWebApplication
if that fails. - ServerContext.GetContext(WebApplication webApplication)
Returns a new instance for the web application’s provider or the farm’s default provider if one is not specified. - ServerContext.GetContext(SPSite site)
ReturnsServerContext.GetContext(site.WebApplication)
. - ServerContext.GetContext(String sharedResourceProviderName)
Returns a new instance for the named provider if it exists, either in the local farm or its parent.
These details inform some simple usage guidelines:
- If you have an
HttpContext
, use the shared instance fromGetContext(HttpContext)
orServerContext.Current
. - If you need a specific SSP, use an overload of
GetContext()
. - Else, use
ServerContext.Default
.
Code Review
The code I was reviewing today was replacing this…
SearchContext.GetContext(SPContext.Current.Site)
…with this…
SearchContext.GetContext(ServerContext.Default)
…to support use of the service in a non-web context. We’re currently working with a single SSP, so Default
should be sufficient; however, we can do better. First, the change in question had to be made in several places. For maintainability, it makes more sense to define a field or property to represent the context wherever we need it. Second, by using ServerContext.Default
we’re missing out on the benefits of the shared instance provided for an HttpContext
, which we usually have.
Dependency Injection
The Dependency Inversion Principle suggests that we should decouple the source of our ServerContext
object from how its consumption. What would that look like here? Well the simplest approach is constructor injection:
private ServerContext { get; set; } public MyService(ServerContext context) { this.ServerContext = context; }
This is the purest form of DI in that the class knows nothing about where the ServerContext
came from, but it’s often practical to “cheat” and expose a nicer interface. In my case, I chose to supplement that constructor with two others:
public MyService() : this(ServerContext.Default) {} public MyService(HttpContext httpContext) : this(ServerContext.GetContext(httpContext) {}
Now my web part can just call MyService(this.Context)
and we still have a convenient default constructor for use in PowerShell. Because everything should be usable from PowerShell. This same idea can be applied to a number of common SharePoint tasks, particularly anywhere you’re using SPContext.Current
, with benefits including improved reusability, maintainability and testability.