SharePoint Calculator Service Part 2 – Service Instance

In Part 1 of this series, we created a basic WCF service contract and the necessary configuration files to host the service in IIS.

In this article, we’ll integrate our service with the SharePoint management experience so that SharePoint administrators can choose which machines in the server farm should host the service.

To do this, we will use the Service Application Framework to create classes that represent our service topology.

Our Calculator service is a web service that will be hosted by IIS, so we will extend the SPIisWebService* classes in the framework.

Here’s the top-level object in our topology that represents the Calculator service in a server farm:

CalculatorService.cs

  1. [Guid("120C39F1-7F39-47AB-8AE4-E6A524EF1094")]
  2. internal sealed class CalculatorService : SPIisWebService
  3. {
  4.     [System.ComponentModel.EditorBrowsable(
  5.         System.ComponentModel.EditorBrowsableState.Never)]
  6.     public CalculatorService()
  7.     {
  8.     }
  9.  
  10.     internal CalculatorService(
  11.         SPFarm farm)
  12.         : base(farm)
  13.     {
  14.     }
  15. }

Since the Service Application Framework classes extend the SharePoint configuration object model (SPPersistedObject), they require a GuidAttribute (line 1) and a default public constructor used for serialization (lines 4-8).

NOTE: For clarity, I will omit the default constructor from the rest of the classes, as it is not really part of the object model but rather an unfortunate requirement for extending SPPersistedObject. There are plenty of benefits for using SPPersistedObject, such as automatic persistence in the SharePoint configuration database and an efficient distributed cache, so it’s worth the price. I use the EditorBrowsable attribute to hide it from Intellisense to reduce the likelihood of someone accidentally using the constructor.

Next, we’ll create the object that represents an instance of our Calculator service hosted on a specific server (SPServer) in the server farm:

CalculatorServiceInstance.cs

  1. [Guid("044CCC9B-B89D-4A9B-833B-4AEE38A77421")]
  2. internal sealed class CalculatorServiceInstance : SPIisWebServiceInstance
  3. {
  4.     internal CalculatorServiceInstance(
  5.         SPServer server,
  6.         CalculatorService service)
  7.         : base(server, service)
  8.     {
  9.     }
  10. }

Now that we’ve created the classes that represent our Calculator service and service instances, we need an installation process to persist these classes into the SharePoint configuration database on a target server farm.

Normally, this would be done using the solution deployment feature, but for now, I’m just going create a simple installer using the System.Configuration.Install.Installer class. In a later article, I’ll demonstrate how to use solution deployment to allow an administrator to deploy your service to an entire server farm (including servers added to the farm in the future) in one step.

Here’s the simplistic installer we’ll use for now:

CalculatorServiceInstaller.cs

  1. [RunInstaller(true)]
  2. public sealed class CalculatorServiceInstaller : Installer
  3. {
  4.     public CalculatorServiceInstaller()
  5.     {
  6.     }
  7.  
  8.     public override void Install(IDictionary stateSaver)
  9.     {
  10.         SPFarm farm = SPFarm.Local;
  11.         if (null == farm)
  12.         {
  13.             throw new InvalidOperationException("SharePoint server farm is unavailable.");
  14.         }
  15.  
  16.         SPServer server = SPServer.Local;
  17.         if (null == server)
  18.         {
  19.             throw new InvalidOperationException("Local server is not joined to a SharePoint server farm.");
  20.         }
  21.  
  22.         // Install executing assembly in the GAC
  23.         string filename = System.Reflection.Assembly.GetExecutingAssembly().Location;
  24.         AssemblyCache.InstallAssembly(filename, false);
  25.  
  26.         CalculatorService service = farm.Services.GetValue<CalculatorService>();
  27.         if (null == service)
  28.         {
  29.             service = new CalculatorService(farm);
  30.             service.Update(true);
  31.         }
  32.  
  33.         if (SPObjectStatus.Online != service.Status)
  34.         {
  35.             service.Provision();
  36.         }
  37.  
  38.         CalculatorServiceInstance serviceInstance = server.ServiceInstances.GetValue<CalculatorServiceInstance>();
  39.         if (null == serviceInstance)
  40.         {
  41.             serviceInstance = new CalculatorServiceInstance(server, service);
  42.             serviceInstance.Update(true);
  43.         }
  44.  
  45.         base.Install(stateSaver);
  46.     }
  47. }

Lines 10-20 get references to the server farm and local server topology objects and do some sanity checking to ensure that the machine running the installer is joined to a SharePoint server farm.

Lines 22-24 add the Calculator service assembly to the GAC, which is required for all classes that extend SPPersistedObject, again, to enable the custom serialization required for persisting the class in the SharePoint configuration database. Note that the AssemblyCache class is something I created for this project, and normally you would use some other technology (an MSI installer package or a SharePoint solution package) to add your assembly to the GAC. I’ll cover this in a future article, but for now we cheat to keep this post short.

Lines 26-31 ensure that the CalculatorService object is registered in the SPFarm.Services collection where the SharePoint administration tools will be able to enumerate it. Note the “true” parameter in the Update method. This parameter should be set to true only when initializing a new persisted object, as it ignores update exceptions that can occur if multiple threads attempt to initialize the object simultaneously.

Lines 33-36 ensure that the CalculatorService object is online.

Lines 38-43 ensure that our service instance is registered with the local server in the SharePoint server farm topology. Note that it will not be online initially. An administrator may optionally start the service instance later to bring it to the online state.

Use installutil.exe (installed with the .NET Runtime) to install the assembly on a server in the server farm. For example:

C:> %SystemRoot%\Microsoft.Net\Framework64\v2.0.50727\InstallUtil.exe Sample.Calculator.Service.dll

So what do we get for this integration work?

Well, SharePoint administrators can now see our Calculator service in the Central Administration web site and can stop and start the service on servers where it has been installed.

Here’s what it looks like on my machine:

Services on Server UI

By default, the service instance is stopped. Administrators can simply click the Start link to start the service on the selected server (“SERVER1” in this example).

Additionally, administrators can use the PowerShell console to enumerate and start and stop service instances. For example:

PS C:\> Get-SPServiceInstance

TypeName                         Status   Id
--------                         ------   --
Microsoft SharePoint Foundati... Online   6251056d-e20d-4915-94c4-caec347c257b
Sample.Calculator.Service.Adm... Disabled 610ccb45-3144-48c0-9521-80ec1dd197bc
Microsoft SharePoint Foundati... Online   ad8b71c4-2d09-42e1-a5e0-31eb6c11ffdc
Microsoft SharePoint Foundati... Online   42fc6565-7633-4be1-8cf2-33141fb929f5
Central Administration           Online   96275fa1-9c95-4fd8-ab78-24ebca189557
Microsoft SharePoint Foundati... Online   9071b881-0cd8-44fc-8852-672eb517f877

PS C:\> $instance = Get-SPServiceInstance | where { $_.TypeName -eq "Sample.Calculator.Service.Administration.CalculatorService" }

PS C:\> Start-SPServiceInstance $instance

TypeName                         Status   Id
--------                         ------   --
Sample.Calculator.Service.Adm... Online 610ccb45-3144-48c0-9521-80ec1dd197bc

If you try this out, you’ll notice that starting and stopping the service instance doesn’t actually change anything in IIS. That’s because we haven’t created a service application yet. And if there is no service application, the service instance (which represents the IIS host process) doesn’t have a logical endpoint to host, in which case the IIS service host isn’t created.

Next time, we’ll implement a Calculator service application, which will allow administrators to deploy our service endpoints to all of the online Calculator service instances in a server farm.