Calling a WCF WebService from a SharePoint WebPart using JavaScript

The following examples demonstrates a JavaScript client calling into a WCF Service which allows updates of SharePoint list data. All deployable from a SharePoint solution (WSP).

The first thing you'll need is the .NET Framework 3.5 installed on your SharePoint server (or VS.NET 2008).

.NET 3.5 Install

https://www.microsoft.com/downloads/details.aspx?FamilyId=333325FD-AE52-4E35-B531-508D977D32A6&displaylang=en

.NET 3.5 SP1 Install

https://www.microsoft.com/downloads/details.aspx?FamilyId=AB99342F-5D1A-413D-8319-81DA479AB0D7&displaylang=en

The second step is to configure the web.config file of your SharePoint site to load the correct assemblies.

In the <configSections> element add the following:

  <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
   <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
     <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere"/>
     <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
     <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
     <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    </sectionGroup>
   </sectionGroup>
  </sectionGroup>

In the <assemblies> section add the following:

<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

In the <pages> section add the following:

<CONTROLS><ADD assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.UI" tagPrefix="asp"></ADD><ADD assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.UI.WebControls" tagPrefix="asp"></ADD></CONTROLS>

In the <httpHandlers> section add the following:

   <remove verb="*" path="*.asmx"/>
   <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
   <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
   <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>

In the <httpModules> section add the following:

   <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

Immediately before the </configuration> element at the bottom of the web.config add the following:

 <system.webServer>
  <validation validateIntegratedModeConfiguration="false"/>
  <modules>
   <remove name="ScriptModule"/>
   <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  </modules>
  <handlers>
   <remove name="WebServiceHandlerFactory-Integrated"/>
   <remove name="ScriptHandlerFactory"/>
   <remove name="ScriptHandlerFactoryAppServices"/>
   <remove name="ScriptResource"/>
   <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
   <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
   <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  </handlers>
 </system.webServer>
 <runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
   <dependentAssembly>
    <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
    <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
   </dependentAssembly>
   <dependentAssembly>
    <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
    <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
   </dependentAssembly>
  </assemblyBinding>
 </runtime>

<system.serviceModel>
    <bindings>
      <webHttpBinding>
        <binding name="webHttpAuthenticated">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Ntlm" />
          </security>
        </binding>
      </webHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="MyWSSListServiceAspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <services>
      <service name="MyWSSListService">
        <endpoint address="" behaviorConfiguration="MyWSSListServiceAspNetAjaxBehavior" binding="webHttpBinding" bindingConfiguration="webHttpAuthenticated" contract="MyWSSListService" />
      </service>
    </services>
  </system.serviceModel>

Note the <system.serviceModel> element. This is the configuration for the WCF service we are about to add.

The next step is to actually create the web part. Create a new SharePoint web part project named DynamicWCFPart (I'm using the Visual Studio Extensions for SharePoint 1.2) but you can use whatever you want :)

Once the webpart project is created change everything you see from WebPart1 to MyDynamicPart (you will need to go into WSP view and change this part too). 

Next add a new SharePoint Template. Name the template item MyWSSListService.svc.

Beneath the Templates directory in Solution Explorer create a new folder called LAYOUTS and beneath LAYOUTS create another folder called MyDynamicWCFPart. Drag the MyWSSListService.svc into the MyDynamicWCFPart folder.

The next step is to add another SharePoint Template to your project. This time name it MyDynamicPartCtrl.ascx. This will be the UI of the webpart.

Once the template is added expand the Templates folder and create a new folder beneath it called CONTROLTEMPLATES then create another folder called MyDynamicWCFPart. Drag the MyDynamicPartCtrl.ascx into the newly created MyDynamicWCFPartfolder.

Finally, your solution should look similar to this:

To get your site ready you will need to create a custom list in your SharePoint site called Clients. The custom list should contain the following fields: Title (the existing one), Address,  City, State and Zip. All of the fields will be of type "Single Line of Text". You should also add some test data into the list as this webpart sample only edits (no inserts).

Next, add the following references:

System.Web.Extensions (3.5.0.0), System.ServiceModel (3.0.0.0), System.ServiceModel.Web (3.5.0.0), System.Runtime.Serialization (3.0.0.0).

Next we'll actually create the WCF service. We'll add the code inline to the .svc file that way it is deployable along with our webpart.

<%@ ServiceHost Language="C#" Debug="true" Service="MyWSSListService" %>

using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using Microsoft.SharePoint;
using System.Collections.Generic;

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyWSSListService
{
    [OperationContract]
    public void UpdateClient(String UniqueID, String PhoneNumber, String Address, String City, String State, String Zip)
    {
        SPWeb spw = SPContext.Current.Web;
        spw.AllowUnsafeUpdates = true;
        SPList spl = spw.Lists["Clients"];
        SPListItem spli = spl.Items[new Guid(UniqueID)];
        spli["Phone"] = PhoneNumber;
        spli["Address"] = Address;
        spli["City"] = City;
        spli["State"] = State;
        spli["Zip"] = Zip;
        spli.Update();
        spw.AllowUnsafeUpdates = false;
    }

    [OperationContract]
    public ClientInformation GetClient(String UniqueId)
    {
        SPWeb spw = SPContext.Current.Web;
        SPList spl = spw.Lists["Clients"];

        SPListItem spli = spl.Items[new Guid(UniqueId)];        
        ClientInformation client =  new ClientInformation();
        client.Name = spli["Title"].ToString();
        client.Address = spli["Address"].ToString();
        client.Phonenumber = spli["Phone"].ToString();
        client.City = spli["City"].ToString();
        client.State = spli["State"].ToString();
        client.Zip = spli["Zip"].ToString();
        client.UniqueId = spli["UniqueId"].ToString();

        return client;
    }

    [OperationContract]
    public List<ClientInformation> GetClients()
    {
        SPWeb spw = SPContext.Current.Web;
        SPList spl = spw.Lists["Clients"];
        List<ClientInformation> clients = new List<ClientInformation>();
       
        foreach (SPListItem spli in spl.Items)
        {
            ClientInformation cl = new ClientInformation();
            cl.Name = spli["Title"].ToString();
            cl.Address = spli["Address"].ToString();
            cl.City = spli["City"].ToString();
            cl.State = spli["State"].ToString();
            cl.Phonenumber = spli["Phone"].ToString();
            cl.Zip = spli["Zip"].ToString();
            cl.UniqueId = spli["UniqueId"].ToString();
            clients.Add(cl);

        }

        return clients;
    }
   
}

[DataContract]
public class ClientInformation
{
    private String _uniqueid = String.Empty;

    [DataMember]
    public String UniqueId
    {
        get { return _uniqueid; }
        set { _uniqueid = value; }
    }

    private String _name = String.Empty;

    [DataMember]
    public String Name
    {
        get { return _name; }
        set { _name = value; }
    }
    private String _address = String.Empty;

    [DataMember]
    public String Address
    {
        get { return _address; }
        set { _address = value; }
    }
    private String _city = String.Empty;

    [DataMember]
    public String City
    {
        get { return _city; }
        set { _city = value; }
    }
    private String _state = String.Empty;

    [DataMember]
    public String State
    {
        get { return _state; }
        set { _state = value; }
    }
    private String _zip = String.Empty;

    [DataMember]
    public String Zip
    {
        get { return _zip; }
        set { _zip = value; }
    }
    private String _phonenumber = String.Empty;

    [DataMember]
    public String Phonenumber
    {
        get { return _phonenumber; }
        set { _phonenumber = value; }
    }

}

Finally, you should build your project to make sure you have all of the references correct.

A few things to note about the WCF service. First, in order to get a reference to the SPContext object correctly the service's class needs to have the [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] attribute added. The second thing to note is the ClientInformation class. It is marked with the [DataContract] attribute along with its members marked with [DataMember] so WCF will know to serialize this to our our client.

Our next step is to add our user interface. Since there is quite a bit of JavaScript involved here I felt it would be easier to design the UI in an ASP.NET User Control (.ASCX).

Open the MyDynamicPartCtrl.ascx control in Visual Studio and paste in the following code:

<%@ Control Language="C#" ClassName="MyDynamicPartCtrl" %>

<asp:ScriptManager ID="scriptManager" runat="server">
    <Services>
        <asp:ServiceReference Path="_layouts/MyDynamicWCFPart/MyWSSListService.svc" />
    </Services>
</asp:ScriptManager>

<script language="javascript" type="text/javascript">
    // Start up - call the service to retrieve our clients from the list
    GetClients();
    function GetClients()
    {
        MyWSSListService.GetClients(OnClientsReceived);
    }
    // Populate our dropdown list with the results of our WCF call    
    function OnClientsReceived(Clients)
    {
        var selClients = document.getElementById('selClients');
        selClients.options.length=0;
        for(var cCount=0; cCount < Clients.length; cCount++)
        {
            var opt = document.createElement("option");
            opt.value = Clients[cCount].UniqueId;
            opt.text = Clients[cCount].Name;
            selClients.options.add(opt);
        }
    }
    // When the user selects a client
    function SelectClient()
    {
        var selClients = document.getElementById('selClients');
        var clientID = selClients.options[selClients.selectedIndex].value;
        MyWSSListService.GetClient(clientID, OnClientSelected);
    }
    // Retrieve the data and populate our form with the selected client
    function OnClientSelected(Client)
    {
        ShowEdit();
        var name = document.getElementById("name");
        name.innerHTML = Client.Name;
        var phone = document.getElementById("phone");
        phone.value = Client.Phonenumber;
        var address = document.getElementById("address");
        address.value = Client.Address;                       
        var city = document.getElementById("city");
        city.value = Client.City;                       
        var state = document.getElementById("state");
        state.value = Client.State;                                   
        var zip = document.getElementById("zip");
        zip.value = Client.Zip;                       
        var hidUniqueId = document.getElementById("hidUniqueId");
        hidUniqueId.value = Client.UniqueId;
    }
    // Code to update the client through the service with the modified data
    function UpdateClient()
    {
        var phone = document.getElementById("phone").value;
        var address = document.getElementById("address").value;
        var city = document.getElementById("city").value;
        var state = document.getElementById("state").value;
        var zip = document.getElementById("zip").value;
        var uniqueid = document.getElementById("hidUniqueId").value;
        MyWSSListService.UpdateClient(uniqueid, phone, address, city, state, zip);
        HideEdit();
    }
    // Helper functions to hide/show our edit form
    function ShowEdit()
    {
        var divEdit = document.getElementById("divEdit");
        divEdit.style.display = "block";
    }
    function HideEdit()
    {
        var divEdit = document.getElementById("divEdit");
        divEdit.style.display = "none";
    }

</script>
<div style="width: 275px">
    <select id="selClients" onchange="javascript:HideEdit()" style="font-size: small">
    </select>
    <input type="button" onclick="javascript:SelectClient()" value="Edit Client" />
   
    <br /><br />
    <div style="display: none" id="divEdit">
        <input type="hidden" id="hidUniqueId" />
        <table style="width: 250px">
            <tr>
                <td colspan="2" style="font-weight: bold">
                    Client Information
                    <hr />
                </td>
            </tr>
            <tr>
                <td>
                    Name
                </td>
                <td>
                    <span id="name"></span>
                </td>
            </tr>            
            <tr>
                <td>
                    Phone
                </td>
                <td>
                    <input type="text" id="phone" />
                </td>
            </tr>
            <tr>
                <td>
                    Address
                </td>
                <td>
                    <input type="text" id="address" />
                </td>
            </tr>
            <tr>
                <td>
                    City
                </td>
                <td>
                    <input type="text" id="city" />
                </td>
            </tr>     
            <tr>
                <td>
                    State
                </td>
                <td>
                    <input type="text" id="state" />
                </td>
            </tr> 
            <tr>
                <td>
                    Zip
                </td>
                <td>
                    <input type="text" id="zip" />
                </td>
            </tr>     
            <tr>
                <td>
                    <input type="button" value="Update Client" onclick="UpdateClient()" />
                </td>
            </tr>                                                                                                       
        </table>
    </div>
</div>

This is definitely not the fanciest UI possible :) What it does is essentially gives you a dropdown list of "clients" to edit. Once you select a client and click the "Edit Client" button the service method GetClient is called. OnClientSelected is the callback when our client is retrieved. From there I populate and show a small form to allow the user to update the list data via the UpdateClient() service method.

To display the user control within the webpart we'll need to dynamically load the control and insert it into the control tree. Open MyDynamicPart.cs and add the following code to your CreateChildControls() method.

UserControl uc = (UserControl)Page.LoadControl("~/_controltemplates/MyDynamicWCFPart/MyDynamicPartCtrl.ascx");
Controls.Add(uc);

The next step is actually a compatability workaround to ensure SharePoint and WCF work well together. For details on the compatability issue see Gille's blog entry here: https://blogs.msdn.com/gzunino/archive/2007/09/17/hosting-a-wcf-service-in-windows-sharepoint-services-v3-0.aspx.

Add a a new C# class to your project and name the file: MyWCFVirtualPathProvider.cs. Replace the code with the following:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Hosting;

public class MyWCFVirtualPathProvider : VirtualPathProvider
{

    public override string CombineVirtualPaths(string basePath, string relativePath)
    {
        return Previous.CombineVirtualPaths(basePath, relativePath);
    }

    public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
    {
        return Previous.CreateObjRef(requestedType);
    }

    public override bool DirectoryExists(string virtualDir)
    {
        return Previous.DirectoryExists(virtualDir);
    }

    public override bool FileExists(string virtualPath)
    {
        // Patches requests to WCF services: That is a virtual path ending with ".svc"     
        string patchedVirtualPath = virtualPath;
        if (virtualPath.StartsWith("~", StringComparison.Ordinal) &&
          virtualPath.EndsWith(".svc", StringComparison.InvariantCultureIgnoreCase))
        {
            patchedVirtualPath = virtualPath.Remove(0, 1);
        }
        return Previous.FileExists(patchedVirtualPath);
    }

    public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath,
               System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }

    public override string GetCacheKey(string virtualPath)
    {
        return Previous.GetCacheKey(virtualPath);
    }

    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        return Previous.GetDirectory(virtualDir);
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        return Previous.GetFile(virtualPath);
    }

    public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
    {
        return Previous.GetFileHash(virtualPath, virtualPathDependencies);
    }

    protected override void Initialize()
    {
        base.Initialize();
    }
}

Next we're going to create an HttpModule to register the virtual path provider we just created. Add another C# class to your project and name it MyWCFPatchupModule.cs. Replace the code with the following:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Hosting;

public class MyWCFPatchupModule : IHttpModule
{

    static bool virtualPathProviderInitialized = false;
    static object virtualPathProviderInitializedSyncLock = new object();

    public void Dispose()
    {
    }

    public void Init(HttpApplication context)
    {
        if (!virtualPathProviderInitialized)
        {
            lock (virtualPathProviderInitializedSyncLock)
            {
                if (!virtualPathProviderInitialized)
                {
                    MyWCFVirtualPathProvider vpathProvider = new MyWCFVirtualPathProvider();
                    HostingEnvironment.RegisterVirtualPathProvider(vpathProvider);

                    virtualPathProviderInitialized = true;
                }
            }
        }
    }
}

You will need to add one more entry to the web.config file of the SharePoint site.

<add name="MyWcfVirtualPathProvider" type="MyWCFPatchupModule, DynamicWCFPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[YourPublicKeyTokenHere]" />

Note you can find your public key token by bringing up a Visual Studio command prompt and from your projects \Debug directory running the following command: sn -T DynamicWCFPart.dll

Deploy your project by right clicking on your project and selecting Deploy.

The next step will be to put your page into edit mode by clicking Site Actions -> Edit Page. Select a zone and click Add a Web Part. Under the miscellaneous category you will see your part MyDynamicPart Web Part. Select it and click Add.

If everything worked you should have something that looks like this.

This example retrieves the data from the list and updates the list with no postbacks. Of course it could use some UI flashiness, error handling, and insert functionality - but this should get you started!