WCF Services and SharePoint: Integrating SharePoint Web Parts and WCF Services Hosted in IIS

I was asked a couple of times recently about WCF and SharePoint—i.e. how do you integrate a WCF service with SharePoint that is hosted in IIS? Well, I did a little digging around over the past few weeks to see what was out there and besides finding Sahil’s blog, which has some decent coverage on deploying WCF to the SharePoint hive, I didn’t find a single place where I had ‘the’ (or should I say ‘my’) perfect walkthrough for the following: deploying a WCF service to IIS and then integrating with SharePoint. (I tend to prefer this option as it maximizes my scalability/usability of the service—inside and out of SharePoint.) And while this perfect doc may exist, I grew a little impatient with my search and figured I would just put one together. So, hopefully you’ll find this useful.

That said, this blog will walk you through the following:

  1. Creation of a WCF service;
  2. Deployment of the WCF service to IIS 7.0;
  3. Creation of a SharePoint web part (ASP.NET web part); and
  4. Modification of the SharePoint web.config.

The core elements of my development environment are as follows:

  • Windows Server 2008
  • WSS 3/MOSS 2007 SP2
  • Visual Studio (VS) 2008 SP1
  • VSEWSS 1.3
  • IIS 7.0

Alright, let’s get started!

1. Creating the WCF Service

First, open VS 2008. Create a blank solution, as you’re going to add a couple of projects to it. Second, right-click the solution and select Add and then New Project. I have my VS instance optimized for C# and select WCF Service Application. Provide a name and location and click OK—see figure below.

image

A number of files are added to the solution. Of specific interest to you should be the IService.cs file and the Service.svc file (and the Service.svc.cs code-behind file). I renamed mine to be a little more intuitive to my example (which will be a simple tax calculator for three states plus federal tax—so essentially calculating net salary for three states)—see figure below. Note that you’ll need to make some amendments in the web.config file to ensure you’ve got the same service and class names in the that file.

image

The service contract (which will be located in the ISalary.cs file) is coded as below, which defines the one class I’m going to include within this service. The code for the contract is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

[ServiceContract]
public interface ITaxCalculator
{

    [OperationContract]
double GetData(double grossSalary, string state);

}

The actual meat of the salary calculation is as follows, which as per the contract will accept a double and string as parameters—returning a double for use in the service implementation.  You can see in the following code that I’m validating what state is being passed to the service and then setting a state tax level based on that state. I’m then making a calculation and returning the net salary.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

public class TaxCalculator : ITaxCalculator
{
double netSalary = 0;
double fedTaxRate = .25;
double stateTaxRate = 0;
double totalTax = 0;

    public double GetData(double grossSalary, string state)
{
if (state == "WA")
{
stateTaxRate = .08;
}
else if (state == "IL")
{
stateTaxRate = .05;
}
else if (state == "CA")
{
stateTaxRate = .12;
}
else if (state == "NY")
{
stateTaxRate = .14;
}

        totalTax = (grossSalary * fedTaxRate) + (grossSalary * stateTaxRate);
netSalary = grossSalary - totalTax;
return netSalary;
}

}

Before you move on to deploying your service, debug your service to make sure there are no errors. Once you’ve got no errors, you can move on to deploying the service.

Note: One thing I reiterate constantly when I present on SharePoint is testing often. This may mean using tools like Fiddler or just employing defensive testing practices such as debugging after each new amendment to your code. Either way, this is one of the best ways to understand not only what is happening with your code, but also to ensure you understand where things may go awry with your code.

2. Deploying the Service to IIS 7.0

With the service code done, the next thing you’ll want to do is deploy the service. This is a fairly simple process and requires you to open IIS 7.0. To do this, click Administrative Tools and select Internet Information Services 7.0. You’ll want to add a new web site, as we will deploy this as an isolated service, so right-click Sites and then select Add Web Site. You’ll then be prompted to add some information for your site. For example, you’ll need a Site name, you’ll need to set an application pool, you’ll need to set the virtual path (which will map to the .svc file we created earlier in this blog—which further references your class/service object), and then you’ll need to assign a port number that is not assigned to 80 (e.g. 774), which is likely your SharePoint server—see figure below. Note that you’ll also want to ensure you’ve got Authentication set properly, so click the Features View tab and select Authentication. Make sure Windows Authentication is enabled.

image

Once you’ve done this, click on the Content tab and you’ll see all of the different files that make up your deployed service in IIS. The figure below shows all of the different files and folders in my service. image

Note that I have not deployed the service DLL to the GAC; my service is being accessed from within the bin folder within the virtual directory mapping. This is evident from the directives in my Salary.svc file, which reads as follows:

<%@ ServiceHost Language="C#" Debug="true" Service="SalaryWCFApp.CalcTax" CodeBehind="Salary.svc.cs" %>

You could deploy your service DLL to the GAC if you wanted, but then you’d need to have a fully qualified reference in your .svc file that pointed to the GAC-deployed DLL.

To test run your service, right-click Salary.svc (or whatever your .svc file is called) and select Browse. This will invoke the service. If you’ve deployed correctly and everything is working, you should be prompted with something similar to the below—the invoked service. If your service invokes correctly, you can now move onto creating the SharePoint web part.

image

3. Creating the SharePoint Web Part

To add the web part project (as mentioned earlier you should have VSEWSS 1.3 installed), right-click the solution and select Add, New Project, select the SharePoint node, and then select the Web Part project template. You’ll need to provide a name for your web part—see figure below.

image

Note that I typically delete the web part item from the web part project and then re-add the web part as an item and then provide the name I want. This is quirky but it is because the default name of the web part is WebPart1, and I find it quicker and easier to just remove and re-add the web part. I called my new web part TaxCalculatorWP. After creating the project, the following files are created for you.

image

I added some code into the TaxCalculatorWP.cs file, which is listed below. The gist of this code is fairly straightforward: it represents a UI that comprises some labels, textboxes, and buttons, which will accept some input parameters, call the WCF tax calculation service and then return (and set one of the textbox Text properties with) the net salary (as a double).

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

namespace TaxCalculator
{
[Guid("e3783c6e-e74a-4871-8865-72de21de4916")]
public class TaxCalculatorWP : System.Web.UI.WebControls.WebParts.WebPart
{

        Label lblTitle = new Label();

        Label lblGrossSalary = new Label();
TextBox txtGrossSalary = new TextBox();

        Label lblStates = new Label();
ListBox lstbxStates = new ListBox();

        Label lblNetSalary = new Label();
TextBox txtNetSalary = new TextBox();

        Button btnCalcSalary = new Button();
Button btnClearFields = new Button();

        string strState = "";
double grossSalary = 0;
double netSalary = 0;

        public TaxCalculatorWP()
{
}

        protected override void CreateChildControls()
{
base.CreateChildControls();

            lblTitle.Font.Bold = true;
lblTitle.Font.Size = 14;
lblTitle.Height = 25;
lblTitle.Width = 300;
lblTitle.Text = "Salary Calculator";
this.Controls.Add(lblTitle);
this.Controls.Add(new LiteralControl("<br>"));
this.Controls.Add(new LiteralControl("<br>"));

            lblGrossSalary.Font.Bold = true;
lblGrossSalary.Height = 25;
lblGrossSalary.Width = 100;
lblGrossSalary.Text = "Grs. Salary:";
this.Controls.Add(new LiteralControl(" "));
this.Controls.Add(lblGrossSalary);

            txtGrossSalary.Height = 25;
txtGrossSalary.Width = 75;
this.Controls.Add(txtGrossSalary);
this.Controls.Add(new LiteralControl("<br>"));
this.Controls.Add(new LiteralControl("<br>"));

            lblStates.Font.Bold = true;
lblStates.Height = 25;
lblStates.Width = 100;
lblStates.Text = "States:";
this.Controls.Add(new LiteralControl(" "));
this.Controls.Add(lblStates);

            lstbxStates.Items.Add("WA");
lstbxStates.Items.Add("IL");
lstbxStates.Items.Add("CA");
lstbxStates.Items.Add("NY");
lstbxStates.Height = 40;
lstbxStates.Width = 150;
this.Controls.Add(lstbxStates);
this.Controls.Add(new LiteralControl("<br>"));
this.Controls.Add(new LiteralControl("<br>"));

            lblNetSalary.Font.Bold = true;
lblNetSalary.Height = 25;
lblNetSalary.Width = 100;
lblNetSalary.Text = "Net Salary:";
this.Controls.Add(new LiteralControl(" "));
this.Controls.Add(lblNetSalary);

            txtNetSalary.Height = 25;
txtNetSalary.Width = 75;
this.Controls.Add(txtNetSalary);
this.Controls.Add(new LiteralControl("<br>"));
this.Controls.Add(new LiteralControl("<br>"));

            btnCalcSalary.Text = "Calc. Salary";
btnCalcSalary.Height = 10;
btnCalcSalary.Height = 40;
this.Controls.Add(btnCalcSalary);
this.Controls.Add(new LiteralControl(" "));

            btnClearFields.Text = "Clear Fields";
btnClearFields.Height = 10;
btnClearFields.Height = 40;
this.Controls.Add(btnClearFields);

            btnCalcSalary.Click += new EventHandler(btnCalcSalary_Click);
btnClearFields.Click += new EventHandler(btnClearFields_Click);

        }

        void btnClearFields_Click(object sender, EventArgs e)
{
txtGrossSalary.Text = "";
}

        void btnCalcSalary_Click(object sender, EventArgs e)
{
strState = lstbxStates.Text;
grossSalary = Convert.ToDouble(txtGrossSalary.Text);

            TaxCalculator.MyTaxCalcWCF.SalaryClient proxy = new TaxCalculator.MyTaxCalcWCF.SalaryClient();
netSalary = proxy.GetData(grossSalary, strState);
txtNetSalary.Text = "$" + netSalary.ToString();
proxy.Close();
}
}
}

If you want to have an intuitive title and description for your web part, then you’ll need to edit the .webpart file, which you can see I’ve done as per the below. You will see this when you browse for the deployed web part in the SharePoint Web part Gallery.

<?xml version="1.0" encoding="utf-8"?>
<webParts>
<webPart xmlns="https://schemas.microsoft.com/WebPart/v3">
<metaData>
<!--
The following Guid is used as a reference to the web part class,
and it will be automatically replaced with actual type name at deployment time.
-->
<type name="e3783c6e-e74a-4871-8865-72de21de4916" />
<importErrorMessage>Cannot import TaxCalculatorWP Web Part.</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">TaxCalculatorWP Web Part</property>
<property name="Description" type="string">Web Part that calculates tax.</property>

</properties>
</data>
</webPart>
</webParts>

You can debug your web part before deploying if you want to by clicking F5. Just remember to click Yes on the Script Debugging Disabled dialog, as per the below figure—which you can opt out of as well if you choose. When you deploy, VS will attach to w3wp and then you can deploy and test the web part.

image

At this point, then, you should have two projects in your solution—similar to the below figure. You can now build and deploy your web part to your local SharePoint server. To do this, right-click the web part project and select Deploy. You’ll note in the VS output window that a number of actions are invoked (e.g. checking for conflicts, resetting IIS, etc.).

image

The one thing I like about VSEWSS 1.3 is that I can amend and re-deploy the web parts and each time VS finds and prompts the resolution of conflicts—as per the figure below. This makes it easy for me to test and redeploy my web parts. For example, I made a number of enhancements when creating my ASP.NET UI, and this feature came in handy when I was doing this. 

image

Note that you might get an error on deployment if you don’t remove the pkg directory. The error will read something like this: “Value does not fall within expected range.” To get past this error, remove the pkg directory and redeploy.

 image

At this point, you should have created, tested and deployed your service and also created, tested and deployed your SharePoint web part. We’re now going to move on to the final stage of the blog: editing the web.config file.

4. Editing the Web.config File

Now this is where things get interesting. Because you’ve deployed the service, and you’ve deployed the web part but there is this little thing called the web.config (your SharePoint server web.config that is) that needs to contain some information for the service to actually run. If you don’t believe me, don’t add anything to the web.config file and then watch how you’ll never get past the postback of the web part. Okay, just take my word for it.

For the above service to work, I added a number of things to the system.servicemodel element in SharePoint’s web.config file. Specifically, I added an entry for the serviceHostingEnvironment with aspNetCompatabilityEnabled set to true, I added the actual service (SalaryWCFApp.CalcTax) along with a number of properties, I added the behavior, I added the bindings, and then I lastly added a client configuration element as well. Note that this information exists; it just lives within the web.config file of your service and the app.config file of your web part. For example, the information for the client element lives in your web part app.config file. Note that if you add an entry for behavior and don’t add a separate entry for it, SharePoint will complain. And to be frank, figuring out what needs to go into the web.config file is probably the trickiest part of this whole blog—and the part that is, in my humble opinion, under-documented.

To edit your SharePoint server web.config navigate to your SharePoint’s root directory (e.g. in my case this is c:\Inetpub\wwwroot\wss\VirtualDirectories\80) and then right-click the web.config and select Edit in Visual Studio 2008. In your web.config, you’ll want to make similar edits that I have in mine below. Note you also may want to add different elements as well.

<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>

  <service name="SalaryWCFApp.CalcTax" behaviorConfiguration="SalaryWCFApp.Service1Behavior">
<endpoint address="https://stefoxdemo.redmond.corp.microsoft.com:774/Salary.svc" binding="wsHttpBinding" bindingConfiguration="NtlmBinding" contract="SalaryWCFApp.ISalary">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>

</services>

<behavior name="SalaryWCFApp.Service1Behavior">
<!-- 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>

<bindings>

<wsHttpBinding>
<binding name="WSHttpBinding_ISalary" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>

<client>
<endpoint address="https://localhost:774/Salary.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ISalary" contract="MyTaxCalcWCF.ISalary" name="WSHttpBinding_ISalary">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>

</system.serviceModel>

So, with all of the above, you should have a fully functional ASP.NET web part that is deployed to SharePoint that you can use to calculate the net salary of a user entering some data. You can test out your salary calculator by adding the web part to a SharePoint site from your Web part Gallery. The result would likely look like the web part in the figure below: you enter in some numbers for the gross salary, select a state, and then click Calc. Salary. And shazam, SharePoint calls the WCF service and calculates the net salary.

image

Now while I know you’re trying to contain your enthusiasm, look beyond the simplicity of the app to the broader ‘pattern.’ This pattern (the integration of an ASP.NET web part with SharePoint and a WCF service deployed to IIS) can be reused across your other web parts or SharePoint applications.

Happy coding!

Steve

Technorati Tags: MSDN,WCF,SharePoint and WCF,SharePoint web part development