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
4: #region IHelloWorldService Members
6: public string SayHello(string inputName)
8: return "Hello " + inputName + ". The site title is: " + SPContext.Current.Site.RootWeb.Title;
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"?>
4: <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
7: <binding name="custombasicHttpBinding">
8: <security mode="TransportCredentialOnly">
9: <transport clientCredentialType="Ntlm"/>
14: .. other code that I took out
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" />
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!)