Getting SPContext.Current in a SharePoint 2007 WCF Service

Posted on 9/28/2008 @ 1:28 AM in #SharePoint by | Feedback | 7814 views

This is a long overdue post.

I have talked previously about WCF as the coolest thing since sliced bread. There are plenty of technologies coming out of Microsoft that make me extremely excited, and WCF is certainly amongst the top few. Please see my previous blogpost about "What is WCF" where I explain what WCF is, and why it is a fundamentally awesome way of writing connected systems and thus your overall architecture.

Another technology that excites me a lot is SharePoint. I have blogged about in great detail of how to make SharePoint and WCF work together. Here is a table of contents for your reference.


- Basics -

1. Create a WCF Home. This is the virtual directory that will host all your WCF endpoints.

2. Create a WCF Service Library, and throw it in the GAC.

3. Create a relevant .svc file in the WCF home you created in step #1.

4. Write a WCF Virtual Path Provider, and register it in the SharePoint site.

- Real world -

1. Adding WCF Support to a website.

2. Deploying WCF EndPoints as solutions.


 

One of the critical challenges however has been that if you were to debug your WCF service running inside SharePoint, SPContext is curiously null. Not only that, HttpContext is null as well. Well that sucks! Why should that be? Let me talk about why this happens by first taking a diversion into WCF and ASP.NET.

WCF and ASP.NET

WCF can be hosted inside your own service, IIS, or windows activation service. ASPNET is hosted inside IIS. Thus, WCF has a scope/architecture that needs to consider beyond just ASP.NET. Thus, requests to WCF services, even if hosted in IIS, do not go through the IIS pipeline. The WCF runtime instead routes them through the WCF transport/channel stack. As a result, by default, HttpContext.Current is null in an IIS6/7 hosted WCF service - why? because ASP.NET never got a hold of it. In fact, there are even further consequences of such separation.

  • WCF and ASP.NET can live inside the same AppDomain, and thus static variables, events etc. can be shared.
  • WCF services follow the tenet of independence of implementation technology - one of the main reasons why WCF is so cool. Thus, by definition, WCF services need to behave consistently irrespective of hosting environment and transport/binding.  As a result,
    • HttpContext is always null when accessed within a WCF service. WCF's equivalent is System.ServiceModel.OperationContext.
    • In ASP.NET, you can change NTFS permissions of a file - and expect IIS7 to pop open an authentication dialog box. This makes no sense in the case of a WCF .svc endpoint. (Anyone wanna take a guess why? Hint: Look at the ASPNET pipeline and check out the portion of the pipeline that serves files - that portion doesn't get called in the case of a WCF .svc endpoint, and thus no file based authentication).
    • For the same reasons as above, putting in <authorization> blocks inside <system.web> make no sense to WCF. Thus they are ignored in the web.config that your WCF endpoint is driven from.
      • The above 2 points are important, so don't be a fool and go around securing your WCF services with System.Web blocks without reading this blogpost to the end.
    • The WCF hosting infrastructure intercepts WCF requests when the PostAuthenticateRequest event is raised and does not return processing to the ASP.NET HTTP pipeline. Thus, HttpModules that are coded to intercept requests at later stages of the pipeline will not get the WCF requests to work upon.
    • If you check the current principal's identity inside a WCF service, you will see that it is running under IUSR_MachineName. Okay, try putting in an <identity impersonate=”true” /> in the System.Web - what do you see? Still the same result. Thus, <identity impersonate="true"/> is ignored for WCF services.

WCF and SharePoint

I don't know who it was .. but .. I heard somewhere, someone say .. "It annoys me when people say SharePoint 2007 is built on ASP.NET, because it is so much more". Uhh .. with due respect, I disagree. Sure there is plenty to learn in SharePoint, but a bad ASPNET dev can never be a good SharePoint dev. Anyway, all of the above have immense implications on SharePoint, because .. SPContext relies on HttpContext, SharePoint relies on HttpModules, ACLs, <identity impersonate="true"/> etc.

LOL, everything that could go awry .. does! :-)

Luckily, a solution exists, and it is as simple as .. making your WCF service ASPNET friendly. Here is how -

1. Make changes to your Service itself.

I am using the same service I demonstrated here. Change the service, and add the System.ServiceModel.Activation.AspNetCompatibilityRequirements attribute as shown below:

   1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
   2: public class HelloWorldService : IHelloWorldService
   3: {
   4:     #region IHelloWorldService Members
   5:  
   6:     public string SayHello(string inputName)
   7:     {
   8:         return "Hello " + inputName + ". The site title is: " + SPContext.Current.Site.RootWeb.Title;
   9:     }
  10:  
  11:     #endregion
  12: }

2. Make changes to the web.config, and thus hosting of the web.config

In the web.config of the host, you need to make the following changes

a) Add the aspnetCompatibilityEnabled = true

b) Add configuration for basicHttpBinding so it requests an NTLM credential (kerberos is included). Now this is the awesome thing about WCF - you can choose to use an alternate credential if you wished :-). Think forms based auth?

The changes are shown as below ---

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
   5:         <bindings>
   6:             <basicHttpBinding>
   7:                 <binding name="custombasicHttpBinding">
   8:                     <security mode="TransportCredentialOnly">
   9:                         <transport clientCredentialType="Ntlm"/>
  10:                     </security>
  11:                 </binding>
  12:             </basicHttpBinding>
  13:         </bindings>
  14:         .. other code that I took out
  15:     </system.serviceModel>
  16: </configuration>
  17:  
 
For the code that I took out, please refer to my previous articles on WCF for the details.

3. Make changes to the WCF client

You need to make two changes,

a) Edit binding configuration so the client sends NTLM creds. This can be done by editing the client's .config as shown below:

   1: <security mode="TransportCredentialOnly">
   2:     <transport clientCredentialType="Ntlm" proxyCredentialType="None"
   3:         realm="" />
   4:     <message clientCredentialType="UserName" algorithmSuite="Default" />
   5: </security>

b) Add the following line your code,

   1: client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;

.. where client is the instance of the proxy being used to call the WCF service.

So far so good.

Now go ahead and deploy all this, call your WCF service, hit Debug, and now you should be able to get a hold of the SPContext.Current :-). That was quite easy, no? :-) However, I'd argue that the circumstances where you must have SPContext.Current are quite limited. Do you disagree? Please tell me why! (And even if you do disagree, you can get a hold of SPContext.Current, so don't complain too much .. I hope!)

Sound off but keep it civil:

Older comments..


On 10/14/2008 4:42:43 PM Ike said ..
Actually, I use SPContext.Current from WCF extensively within custom applications that live inside of SharePoint but use non-SharePoint data sources. I have put together a nested SharePoint master page whose sole purpose is turning the main content area into an ExtJs panel. Child pages then construct the ExtJs user interface and any communication with the server is done via WCF services that use a json behavior. Even though non-SharePoint data sources are utilized, SharePoint is used as the security mechanism so most service operations check to see if the current SharePoint user has certain custom rights, is a member of a SharePoint group, etc.


On 12/16/2008 5:08:56 AM Soren said ..
Thanks for posting this article series! However, I cannot get things working properly when accessing SPContext within a WCF service. When debugging, SPContext.Current is not null but cannot be evaluated either. "Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack." is what the debugger evaluates the object to.


Funny, when embracing in try/catch block, control is not given to the catch block but returned directly to the client, reading

"An error occurred while receiving the HTTP response to http://server/_vti_bin/_wcf/service.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details."

In order to get things working in the first place, I had to do a couple of modifications not documented in your acticle:

<service behaviorConfiguration=" ... " name=" ... ">


<!-- <endpoint address="" binding="wsHttpBinding" contract=" ... " /> -->


<endpoint address="" binding="basicHttpBinding" contract="IContract" />


i.e. change service from wsHttpBinding to basicHttpBinding.

and client-side go with basicHttpBinding to get <security mode="TransportCredentialOnly"> available.


On 2/18/2009 4:08:51 PM Oren said ..
have a problem with SPContext.Current: the site collection i am working with is http://localhost/sites/test1


but SPContext.Current returns http://localhost.

please help


On 3/10/2009 5:01:09 AM saumil said ..
I want to get the context of current web.... From custom web service which is deployed in _vti_bin(ISAPI) Folder


If run web service from Http://servername/sites/xyz/default.aspx


then i need xyz in spweb from spcontext.current.web

but i m getting only "Http://servername"

thanx


On 5/20/2009 7:56:12 AM Adrian Hara said ..
I've the same problem as Oren. The context doesn't seem to be "subweb-sensitive", it always returns the root web (in my case I'm calling the wcf service from a silverlight client hosted in a subweb). Any workarounds?


On 5/20/2009 7:56:50 AM Adrian Hara said ..
Forgot to say, my wcf service is deployed under _layouts somewhere...


On 6/23/2009 3:42:16 AM Hans said ..
I suspect you should load the SPContext from the current HttpContext. using e.g. SPContext.GetContext(HttpContext); Why? Internal code seems to break. Assure yourself proper access to SharePoint.


On 6/23/2009 1:09:11 PM Sahil Malik said ..
Hans - can you give an example of what breaks?


Thanks,

S


On 7/3/2009 2:21:34 AM Priya said ..
Hey Sahil,


Apologies if this has already been covered before..is it possible for me to deploy an AJAX web part that in turn invokes the WCF service? I'm wondering whether NTLM based authentication would fail in such a scenario.

Thanks,


Priya


On 7/3/2009 11:09:08 AM Sahil Malik said ..
Priya,

Yes this is possible. Just use webHttpBinding, with enableWebScript behavior. NTLM identity will be passed along, as long as you are using an asp.net compatible wcf service. It should all work seamlessly.

Sahil


On 9/15/2009 2:38:26 AM Vishal said ..
Hi,


The above steps are very helpfull for me, but I am stuck with the following line,

client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;

I am not getting "Windows.AllowedImpersonationLevel" in my project. I am using silverlight 3.

Can you please help me on this.


On 9/15/2009 6:42:14 AM Sahil Malik said ..
Oye Vishal - That is not a part of the Silverlight .NET framework.


See my webcast on dnrtv (webcast #142) on how to integrate Silverlight with SharePoint.

S


On 1/11/2011 2:19:59 PM Igor Tsyselsky said ..
Hi everyone.


I am try get SPContext.Current from WCF REST, but i cant.


When i try return this string service is stop work.


..


return SPContext.Current.Site.RootWeb.Title;


..


any other data, work is well

web.config


==================================================================


<system.serviceModel>


<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />


<bindings>


<webHttpBinding>


<binding name="customWebBinding">


<security mode="TransportCredentialOnly">


<transport clientCredentialType="Ntlm" />


</security>


</binding>


</webHttpBinding>


</bindings>


<services>


<service name="RESTfulService.Project" behaviorConfiguration="ProjectServiceBehavor">


<endpoint address=""


behaviorConfiguration="ProjectEndpointBehavor"


binding="webHttpBinding"


contract="RESTfulService.IProject"/>


<endpoint address="mex"


binding="mexHttpBinding"


contract="IMetadataExchange" />


<host>


<baseAddresses>


<add baseAddress="http://develop:2011/_wcf/" />


</baseAddresses>


</host>


</service>


</services>


<behaviors>


<endpointBehaviors>


<behavior name="ProjectEndpointBehavor">


<webHttp/>


</behavior>


</endpointBehaviors>


<serviceBehaviors>


<behavior name="ProjectServiceBehavor">


<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>


<serviceDebug includeExceptionDetailInFaults="false" />


</behavior>


</serviceBehaviors>


</behaviors>


</system.serviceModel>


====================================================================


[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]


class Project: IProject


{


public string GetAllProject()


{


return SPContext.Current.Site.RootWeb.Title;

}


}


On 4/24/2012 11:01:45 AM Andre said ..
4 years now and still usefull!


Just to help those having the same problem like Oren... if you follow instructions on http://msdn.microsoft.com/en-us/library/ms464040.aspx and you pay attention to not use the method .Discover (on your web service) then you will probably be able to use SPContext.Current.Web in the subsite context :)