Creating Silverlight + WCF Apps in SharePoint 2007

I’ve seen people asking how to create WCF services from within SharePoint 2007. It’s actually really easy – much easier than ASMX web services. I could go into a lot of detail but here’s the basic steps:

  1. Create or get a package to modify the SharePoint 2007 web.config to understand WCF URL requests.
    • WCF Service requests start with ‘~’ which SharePoint doesn’t like. You need a custom HTTPModule you can add to SharePoint’s web.config to provide a virtual path provider to strip this character from the request. This is easy to find.
  2. Create a solution that contains your projects.
    • WCF Service Project. You can only target up to .NET 3.5 because SharePoint can’t run in the .NET 4 runtime and if you want your service hosted in SharePoint (to get the nifty SPContext stuff) your service will have to run in the same runtime. See notes below about specific web.config settings.
    • Web Project. This is typically a very simple project and is likely just a simple ASP.NET page with the SharePoint master page instructions and basic layout with the Silverlight app referenced.
    • Silverlight Project. The meat of your application. Create a Web Service reference to your WCF service and watch the magic. Oh, it’s not working? Yeah, there’s a few things you need to do here.

The above is pretty straight forward but won’t work until you sprinkle in some magic.

Decorate your service method with the AspNetCompatibilityRequirementsMode.Required attribute:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

WCF Services can run in an optimized pipeline to speed them up. Unfortunately this means they will run outside the traditional pipeline for ASP.NET applications. Decorating your service method this way forces your service to run in the ASP.NET pipeline. This is necessary in order for IIS to give the request an HTTPContext and for SharePoint to make an SPContext available.

The downside to this is that you can just forget about using polling duplex communication mode. (Well, you don’t have to forget about it but you’re on your own for solving this problem…) Why? Long story short the session state manager locks access on each request. The polling duplex communication works by initiating long-timeout requests to the server. If the server has nothing to say, the request eventually times out and a new request is initiated. This timeout isn’t super long in order to ensure you haven’t lost connection rather than the server has nothing to say but it is long enough to cause a slight problem. When the request is active, all other service calls are queued up because they can’t access the session state! As soon as the polling connection times out, the other requests can try their hand at executing while the polling connection reconnects and starts blocking again.

This is almost never what you want since the biggest advantage to the forced-async programming environment of Silverlight enables highly responsive designs. You could write your own session state handler with different locking mechanisms or tweak the polling timeout at the cost of increasingly talkative connections or you can go the route I prefer: create a heartbeat method using a Timer and firing an event if the service response contains something in which you’re interested. This gives you good granularity for how often your clients should ping the server with not too much more trouble. (It took me less time to roll this in a singleton connection class than it did to figure out how to configure the service endpoints for the polling duplex connection and then debug the queued service requests until I figured out what was happening.)

Configure your endpoints to use NTLM transport. This is to enable the user authentication to be passed over the wire and for SharePoint to treat the connection as if it were a browser user. SharePoint will even return the correct user in the SPContext if you interactively sign in as a different user in SharePoint. You don’t have to maintain separate user account management in your application.

<?xml version="1.0"?>
<configuration>
   <connectionStrings>
      <add name="WRDataConnectionString" connectionString="[...];Integrated Security=True" providerName="System.Data.SqlClient"/>
   </connectionStrings>
   <system.web>
      <compilation debug="false" />
      <customErrors mode="Off"></customErrors>
   </system.web>
   <system.serviceModel>
      <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
      <bindings>
         <basicHttpBinding>
            <binding name="customBasicHttpBinding">
               <security mode="TransportCredentialOnly">
                  <transport clientCredentialType="Ntlm" />
               </security>
            </binding>
         </basicHttpBinding>
      </bindings>
      <services>
         <service behaviorConfiguration="AppServiceBehavior" name="App.WCF.Services.AppService">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="customBasicHttpBinding" contract="App.WCF.Services.IAppService">
               <identity>
                  <dns value="localhost" />
               </identity>
            </endpoint>
         </service>
      </services>
      <behaviors>
         <serviceBehaviors>
            <behavior name="AppServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
               <serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
               <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
         </serviceBehaviors>
      </behaviors>
   </system.serviceModel>
</configuration>

One thing to note in the above is no address in our endpoint. This is because we have to deploy to many environments, all of which use different naming. Instead of specifying a static address, we actually provide the service base address to the Silverlight application as part of its initial startup parameters in its hosting page using SPContext.Current to pass the current web URL. We just concatenate the service name to the end. This makes it much easier to deploy to various SharePoint environments.

If you happen to run SSL you will need to modify your web config to handle this.

<?xml version="1.0"?>
<configuration>
   <connectionStrings>
      <add name="WRDataConnectionString" connectionString="[...];Integrated Security=True" providerName="System.Data.SqlClient"/>
   </connectionStrings>
   <system.web>
      <compilation debug="false" />
      <customErrors mode="Off"></customErrors>
   </system.web>
   <system.serviceModel>
      <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
      <bindings>
         <basicHttpBinding>
            <binding name="customBasicHttpBinding">
               <security mode="Transport">
                  <transport clientCredentialType="Ntlm" />
               </security>
            </binding>
         </basicHttpBinding>
      </bindings>
      <services>
         <service behaviorConfiguration="AppServiceBehavior" name="App.WCF.Services.AppService">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="customBasicHttpBinding" contract="App.WCF.Services.IAppService">
               <identity>
                  <dns value="localhost" />
               </identity>
            </endpoint>
         </service>
      </services>
      <behaviors>
         <serviceBehaviors>
            <behavior name="AppServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
               <serviceMetadata httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
               <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
         </serviceBehaviors>
      </behaviors>
   </system.serviceModel>
</configuration>

Your Silverlight clientconfig will need to match your endpoint to get a successful connection.

With these slight tweaks, creating SharePoint 2007 hostable WCF Services consumable by Silverlight is easy! Use a packager like WSPBuilder and building the deployment WSPs are easy too (just follow the necessary folder structure). For simplicity we deploy to subfolders in the LAYOUTS directory but you can get as creative as you like.

See you space cowboy.
-Erik

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s