SharePoint 2007 as a WCF host - Step #4, Write a Virtual Path Provider

Posted on 9/8/2008 @ 9:13 PM in #SharePoint by | Feedback | 12706 views

Table of Contents for SharePoint as a WCF host


- 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.


As I just mentioned, the SPVirtualPathProvider blows up when it see's a URL that starts with '~'. Luckily, the problem is easy to fix. You simply author your own Virtual Path Provider.

In order to write your own VirtualPathProvider, you have to author a class that inherits from the System.Web.Hosting.VirtualPathProvider class. Now, you might be thinking that this will be a very difficult job - considering the SPVirtualPathProvider does SO MUCH work in making the content database paths, and physical paths work together. If I had to do all that work, I'd be howling and screaming like a hungry dog that just got kicked. Thankfully, the VirtualPathProviders work in a chained form - so all you've gotta do is, chain yours AFTER the SPVirtualPathProvider. (I'll talk soon about how to register your Virtual Path Provider).

This way, the previous VirtualPathProvider (in our case, the SPVirtualPathProvider), is accessible using the "Previous" property on the VirtualPathProvider class.

Thus, my custom VirtualPathProvider looks like this -

   1:  public class WCFVirtualPathProvider : VirtualPathProvider
   2:  {
   3:      public override string CombineVirtualPaths(string basePath, string relativePath)
   4:      {
   5:          return Previous.CombineVirtualPaths(basePath, relativePath);
   6:      }
   7:   
   8:      // all other methods omited, they simply call Previous... like the above.
   9:   
  10:      public override bool FileExists(string virtualPath)
  11:      {
  12:          string fixedVirtualPath = virtualPath;
  13:          if (
  14:            virtualPath.StartsWith("~") &&
  15:            virtualPath.EndsWith(".svc")
  16:              )
  17:          {
  18:              fixedVirtualPath = virtualPath.Remove(0, 1);
  19:          }
  20:          return Previous.FileExists(fixedVirtualPath);
  21:      }
  22:   
  23:      protected override void Initialize()
  24:      {
  25:          base.Initialize();
  26:      }
  27:  }

So really, my funky logic goes in a single method - the FIleExists method.

Okay, fantastico. The next question is, how the heck do I register this VirtualPathProvider into my SharePoint / Port 80 site? Well the code to do so is quite simple as shown below:

   1:  WCFVirtualPathProvider wcfVPP = new WCFVirtualPathProvider();
   2:  HostingEnvironment.RegisterVirtualPathProvider(wcfVPP);

But, the above must be executed ONLY once. ONLY Once - after each IISRESET :-). Yep, ASP.NET forgets the virtual path providers it was using if you issue an IISReset, or modify the web.config - basically everytime the application recompiles.

So, the mostest bestest place to put such code, is inside an HttpModule, and use a static variable to check and see that you don't register this VirtualPathProvider over and over again. Here's the code for the HttpModule -

   1:  public class WCFVPPRegModule: IHttpModule
   2:  {
   3:      static bool wcfProviderInitialized = false;
   4:      static object locker = new object();
   5:   
   6:      public void Init(HttpApplication context)
   7:      {
   8:          if (!wcfProviderInitialized)
   9:          {
  10:              lock (locker)
  11:              {
  12:                  if (!wcfProviderInitialized)
  13:                  {
  14:                      WCFVirtualPathProvider wcfVPP = new WCFVirtualPathProvider();
  15:                      HostingEnvironment.RegisterVirtualPathProvider(wcfVPP);
  16:                      wcfProviderInitialized = true;
  17:                  }
  18:              }
  19:          }
  20:      }
  21:  }

Ok good job.

Now put both of the above in the GAC (I put them both as 2 classes in a single assembly called WCF Support). Modify the web.config to register the HttpModule - which in turn will register the WCFVirtualPathProvider. This is as shown below:

   1:  <add 
   2:     name="WCFVPPRegModule" 
   3:    type="WCFSupport.WCFVPPRegModule, WCFSupport, Version=1.0.0.0, Culture=neutral,PublicKeyToken=d4f7bd9e55b39661"
   4:  />

Windows 2008 Only - In IIS7 Mgr, double click on authentication settings for the SharePoint website you're fiddling with, and "Enable" anonymous authentication. Before you freak out, all this is doing is enabling IUSR to authenticate to the website - your SharePoint site is still protected, non-anonymous. But if you don't believe me, go check it yourself in SharePoint central admin, and by browsing to the site itself. If you don't wanna do this, you'll have to make _wcf as it's own Virtual Directory, and put anon only there. But the VirtualPathProvider will quit working if you do that. Basically the VirtualPathProvider will be given a URL such as ~/HelloWorld.svc, instead of ~/_wcf/HelloWorld.svc, and the SharePoint VirtualPathProvider will look for http://yourmosssite/HelloWorld.svc - and obvious fail. Unfortunately, you don't quite have the HttpContext ready to differentiate between a virtual directory and a website URL, so you'll just have to go with the not so cool choice of dumbing down your VirtualPathProvider to .. "If I see .svc - always return true" -- i.e. Assume File Always Exists --> Not recommended frankly.

NICE.

Now, browse to your WCF service, and you should merrily see the below -

.. Umm .. that's basically it. You can now start using the SharePoint object model in the WCF service, and merrily expose SharePoint functionality on a variety of endpoints. Now THATS cool!

Sound off but keep it civil:

Older comments..


On 5/14/2008 5:58:22 PM Steve Clements said ..
That is indeed cool...great series :)


On 10/13/2008 9:34:31 AM Daniel Garcia Lopez said ..
Cool Man!!!. Thanks.


On 1/12/2009 10:12:36 AM lex said ..
Thanks so much for this post. I have successfully followed your instructions. Have you or anyone you know, tried to do a REST style WCF service. I've done one but have not got it to work, i keep getting 404 error. (ie. http://spserver/_vti_bin/_wcf/MyService.svc works, but http://spserver/_vti_bin/_wcf/MyService.svc/resource gives me a 404). This uri template works outside of sharepoint. I would like to get it to work in sharepoint as I see great potential in getting other SharePoint related service exposed similarly.


On 1/19/2009 9:37:48 AM Lars Berg said ..
Any news wether this will work with REST Style Services ?


On 1/19/2009 8:09:52 PM Sahil Malik said ..
Lars - yes it will.

S


On 2/12/2009 11:54:38 AM Lars Berg said ..
Hi again.

I got REST Services working on my XP machine - so its possible yes.

But I have a new problem now.


I've ported the code that worked before on the XP machine to Windows Server 2008 - and I now can't get it to work.

I have the same problem as lex. The .svc is working fine - but when I try to access one of the REST Operations I get a 404 error.

When accessing the .svc the Winsmarts code is running (I debug it).


When accessing an Operation on the REST service - the Winsmart code is not touched. It just directly returns a 404.

Any help to go in the right direction would be appriciated :)


It might just be me that can't remember a step I took on the XP machine to get it working there.


On 2/13/2009 5:45:27 AM Lars Berg said ..

On 3/23/2009 8:39:33 AM Gerry Laenen said ..
Thanks for this wonderful tip!

In your custom VirtualPathProvider you remove the '~' in front of the Url, which is not ideal in many situation. ~ points to the root folder of your web site, which will limit your solution to put your services in the root folder of your web site. Ideally, your services run in a virtual directory under your web site. To make a dynamic reference in virtual directories, I have adapted your piece of code where your replace the '~'.

I have replaced the code by:


fixedVirtualPath = this.CombineVirtualPaths("/", virtualPath);

I am looking forward using WCF and SharePoint together now :).

Best regards


On 3/23/2009 9:12:09 AM Sahil Malik said ..
Hey Gerry,

Thanks for your comment.

I think there should be no limitation on where the services run - in a Virtual Dir, or a real dir. The above code is for real dir, your approach is for a virtual dir. I think the best approach would be to use System.Web.HostingEnvironment, and figure out exactly where the your service is running, and take the appropriate action .. i.e. .. not hardcode it to a real dir or a virtual dir.

BTW, you said that "Ideally, your services run in a virtual directory under your web site"

Why is that?

S


On 4/22/2009 8:55:22 AM Helge Norvang said ..
Hi,

I have followed your instructions carefully and I'm really close to getting this to work, HOWEVER, it doesn't. I'm getting the following error: "Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service.". This is on a WSS 3.0 server and it's an intranet so enabeling anonymous access in IIS is out of the question. What the heck do I need to do?

Thanks!


On 4/24/2009 12:14:27 AM Sahil Malik said ..
Helge,

Nice to hear from you :)

Are you running your WCF service in ASPNET compatibility mode? Search my blog for getting SPContext.Current in a WCF Service.

S


On 8/10/2009 5:25:26 AM Priyanka said ..
Hi sahil,

Good work ..I am using a WCF service in sharepoint webpart . I have created the wcf service ( as mentioned in your post) .. But i am getting the File not found error..


I am not able to get through it can un help me...


******************************************************************************************


Error:


Server Error in '/_wcf' Application.


The resource cannot be found.


Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested URL: /_wcf/SvcLicensePublisher.svc

Version Information: Microsoft .NET Framework Version:2.0.50727.3082; ASP.NET Version:2.0.50727.3082


On 9/22/2009 5:06:38 AM Adrien said ..
Hello,


I have a problem with my WCF service.


I put my WCF service on IIS 6.0 (windows 2003) on the adress http://localhost/WCF/, it's working. This WCF service calls SharePoint webservices. My silverlight application calls my WCF service, it's working but not perfectly because when it's a function like a get or a check in a SharePoint list, it's working, but when it's an Add, i have an error message : "CommunicationException was unhandled by user code", and "The remote server returned an error: NotFound." from VisualStudio.


A last thing, when i used the WCF service of VisualStudio, it's working perfectly.


Please help :) Thanks by advance


On 11/22/2010 3:02:43 AM Peter said ..
Sahil,

I am hosting SP 2007 on Windows Server 2003 and IIS 6.0 - I remarked following problem when running .svc:

Exception: System.ServiceModel.ServiceActivationException: The service '/_vti_bin/_wcf/TestService/TestService.svc' cannot be activated due to an exception during compilation. The exception message is: Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.. ---> System.NotSupportedException: Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.

Then I tried to enable anonymous access on Web Application in IIS 6.0 and error disappeared - service started to work.


My conclusion - section marked as for Windows Server 2008 and IIS 7.0 only is applicable also for Windows Server 2003 and IIS 6.0


On 8/15/2011 12:00:25 PM Johnny Housley said ..
I am having issues with this implementation. I am getting a StackOverflow exception as the app pool dies with the sample.

Any ideas?

Thank you!


On 10/18/2011 4:47:40 PM pravin said ..
What if my WCF services are AJAX enabled.


1> I have a WCF service deployed at http://root/site/_vti_bin/abc/service1.svc


2> The endpoint is using a ServiceFactory hence there is no service configuration in Web.Config file


3> I registered the service reference in AJAX script manager on a page


4> On the client side I created proxy by, var proxyObj = ServiceNamespace.Service1();


4> Called the method on the Service by, proxyObj.GetSiteName(success,fail);


5> This returns a wrong site name as the SPContext available to the service belongs to the root.


6> After debugging it shows that the jsdebug file generated dynamically for the endpoint had "_vti_bin/abc/Serivce1.svc".

If this is the reason why it takes root webapp's Site Name then how to fix the jsdebug file. If not does your provider take care of getting correct context?