SharePoint Calculator Service Part 4 – New Service Application UI

In Part 3 of this series, we created a service application class that represents a logical endpoint of our Calculator service in the SharePoint server farm topology.

We also created a temporary installer (a batch file) to put our service files in the right places on the disk. (We’ll revisit the proper way to do this in a future article.)

And finally, we saw how an administrator can use PowerShell commands to create a new service application object and deploy our service to any server in the farm.

Pretty neat, except that the PowerShell commands were far from intuitive. Time to fix that.

In this article, we’ll see how to integrate our service application creation and provisioning experience with the SharePoint Central Administration web site. This will allow administrators to simply point and click to deploy a Calculator service application to servers in the farm.

The first thing we’ll do is implement the IServiceApplicationAdministration interface on our CalculatorService class:

CalculatorService.cs

  1. internal sealed class CalculatorService : SPIisWebService, IServiceAdministration
  2. {
  3.     #region IServiceAdministration Members
  4.  
  5.     public Type[] GetApplicationTypes()
  6.     {
  7.         return new Type[] { typeof(CalculatorServiceApplication) };
  8.     }
  9.  
  10.     public SPPersistedTypeDescription GetApplicationTypeDescription(
  11.         Type serviceApplicationType)
  12.     {
  13.         if (serviceApplicationType != typeof(CalculatorServiceApplication))
  14.         {
  15.             throw new NotSupportedException();
  16.         }
  17.  
  18.         return new SPPersistedTypeDescription(
  19.             "Calculator Service",
  20.             "Performs calculations to demonstrate the SharePoint Service Application Framework.");
  21.     }
  22.  
  23.     public override SPAdministrationLink GetCreateApplicationLink(
  24.         Type serviceApplicationType)
  25.     {
  26.         return new SPAdministrationLink("/_admin/sample/calculator/createapplication.aspx");
  27.     }
  28.  
  29.     public override SPCreateApplicationOptions GetCreateApplicationOptions(
  30.         Type serviceApplicationType)
  31.     {
  32.         // Do not support farm creation wizard
  33.         return SPCreateApplicationOptions.None;
  34.     }
  35.  
  36.     public SPServiceApplication CreateApplication(
  37.         string name,
  38.         Type serviceApplicationType,
  39.         SPServiceProvisioningContext provisioningContext)
  40.     {
  41.         throw new NotSupportedException();
  42.     }
  43.  
  44.     public SPServiceApplicationProxy CreateProxy(
  45.         string name,
  46.         SPServiceApplication serviceApplication,
  47.         SPServiceProvisioningContext provisioningContext)
  48.     {
  49.         throw new NotSupportedException();
  50.     }
  51.  
  52.     #endregion
  53. }

The GetApplicationTypes method (lines 5-8) tells SharePoint the types of service applications that are supported by our service. In this case, we have one service application type:  CalculatorServiceApplication.

The GetApplicationTypeDescription method (lines 10-21) return a user-friendly description of our service application type for display purposes.

The GetCreateApplicationLink method (lines 23-27) return the URI of the ASPX page that provides the user interface for creating our service application. We’ll need to create this page later.

We don’t need to implement the remaining interface methods for now—they are used for integrating with the Farm Configuration Wizard, which we’ll cover in a future article.

Let’s take a look at what we get for this work:

NewServiceAppMenu

Nice! The Service Applications management page has a new “Calculator Service” drop-down menu item.

If you click “New”, “Calculator Service” right now, you’ll see a “Page Not Found” error in your browser because we haven’t created the “createapplication.aspx” page yet. So let’s create it:

CreateApplication.aspx.cs

  1. public class CalculatorServiceApplicationCreatePage : GlobalAdminPageBase
  2. {
  3.     protected TextBox ServiceApplicationNameTextBox;
  4.     protected IisWebServiceApplicationPoolSection ServiceApplicationPoolSection;
  5.  
  6.     protected override void OnLoad(EventArgs e)
  7.     {
  8.         ((DialogMaster)Page.Master).OkButton.Enabled = true;
  9.  
  10.         base.OnLoad(e);
  11.     }
  12.  
  13.     protected override void OnInit(EventArgs e)
  14.     {
  15.         ((DialogMaster)Page.Master).OkButton.Click += new EventHandler(this.OkButton_Click);
  16.  
  17.         base.OnInit(e);
  18.     }
  19.  
  20.     protected void OkButton_Click(object sender, EventArgs e)
  21.     {
  22.         if (Page.IsValid)
  23.         {
  24.             CalculatorService service = SPFarm.Local.Services.GetValue<CalculatorService>();
  25.             if (service != null)
  26.             {
  27.                 using (SPLongOperation longOperation = new SPLongOperation(this))
  28.                 {
  29.                     longOperation.Begin();
  30.  
  31.                     string serviceApplicationName = ServiceApplicationNameTextBox.Text.Trim();
  32.  
  33.                     CalculatorServiceApplication serviceApplication = new CalculatorServiceApplication(
  34.                         serviceApplicationName,
  35.                         service,
  36.                         this.ServiceApplicationPoolSection.GetOrCreateApplicationPool());
  37.  
  38.                     serviceApplication.Update();
  39.  
  40.                     serviceApplication.Provision();
  41.  
  42.                     longOperation.EndScript("window.frameElement.commitPopup();");
  43.                 }
  44.             }
  45.             else
  46.             {
  47.                 throw new InvalidOperationException("The Calculator service is not installed.");
  48.             }
  49.         }
  50.     }
  51. }

The protected members (lines 3-4) reference controls that will be used to prompt for the new service application name and application pool.

The OnLoad and OnInit methods (lines 6-18) enable the “OK” and “Cancel” buttons on the page, which is displayed as a dialog.

The OkButton_Click method (lines 20-51) is called when the OK dialog button is clicked.

Line 24 looks up our CalculatorService object in the configuration database. If it’s not found, we throw an exception (line 47). (It should be installed, otherwise the link to this page in the “New” drop-down menu wouldn’t be there.)

Lines 27-29 start a new SPLongOperation, which displays a fancy progress bar. This will give the administrator something nice to look at while SharePoint churns away deploying the new service application to all of our online service instances in the farm.

Lines 33-38 create a new service application from the administrator input and persist it in the configuration database.

Line 40 is where the magic happens. The Provision method instructs SharePoint to deploy the service application to all online Calculator service instances in the server farm, to include creating an IIS web site and virtual directory as necessary to host our WCF service.

And finally, line 42 ends the SPLongOperation by closing the dialog window.

Here’s the matching ASPX page:

  1. <%@ Page Language="C#" Inherits="Sample.Calculator.Service.UI.Administration.CalculatorServiceApplicationCreatePage, Sample.Calculator.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d5d38459f57e2f46" MasterPageFile="~/_layouts/dialog.master" %>
  2. <%@ Assembly Name="Microsoft.SharePoint.ApplicationPages.Administration, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
  3. <%@ Assembly Name="Microsoft.SharePoint.ApplicationPages.Administration" %>
  4. <%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  5. <%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="~/_controltemplates/InputFormSection.ascx" %>
  6. <%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="~/_controltemplates/InputFormControl.ascx" %>
  7. <%@ Register TagPrefix="wssuc" TagName="IisWebServiceApplicationPoolSection" src="~/_admin/IisWebServiceApplicationPoolSection.ascx" %>
  8. <%@ Import Namespace="Microsoft.SharePoint" %>
  9. <%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
  10.  
  11. <asp:Content ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
  12.     <wssawc:FormDigest ID="FormDigest" runat="server" />
  13. </asp:Content>
  14.  
  15. <asp:Content ContentPlaceHolderId="PlaceHolderDialogHeaderPageTitle" runat="server">
  16.     <asp:Literal ID='PageTitle' Text="Create New Calculator Service Application" runat="server" />
  17. </asp:Content>
  18.  
  19. <asp:Content contentplaceholderid="PlaceHolderDialogDescription" runat="server">
  20.     <asp:Literal ID='PageDescription' Text="Specify the settings for this new service application." runat="server" />
  21. </asp:Content>
  22.  
  23. <asp:content contentplaceholderid="PlaceHolderDialogBodyMainSection" runat="server">
  24.     <table border="0" cellspacing="0" cellpadding="0" width="100%" class="ms-propertysheet">
  25.         <wssuc:InputFormSection
  26.             Title="Name"
  27.             runat="server">
  28.             <Template_InputFormControls>
  29.                 <wssuc:InputFormControl LabelText="Service Application Name" runat="server" >
  30.                     <Template_Control>
  31.                         <wssawc:InputFormTextBox
  32.                             id="ServiceApplicationNameTextBox"
  33.                             title="Name"
  34.                             maxlength="256"
  35.                             columns="35"
  36.                             class="ms-input"
  37.                             runat="server" />
  38.                         <wssawc:InputFormRequiredFieldValidator
  39.                             ID="ServiceApplicationNameValidator"
  40.                             ControlToValidate="ServiceApplicationNameTextBox"
  41.                             ErrorMessage="You must specify a value for this required field."
  42.                             runat="server" />
  43.                     </Template_Control>
  44.                 </wssuc:InputFormControl>
  45.             </Template_InputFormControls>
  46.         </wssuc:InputFormSection>
  47.  
  48.         <wssuc:IisWebServiceApplicationPoolSection
  49.             id="ServiceApplicationPoolSection"
  50.             runat="server" />
  51.     </table>
  52. </asp:content>

And here’s a new line in our temporary installer batch file to copy the ASPX page to the right place on disk:

xcopy /y "%_SETUPPATH%\UI\Admin\*.aspx" "%_INSTALLPATH%\template\admin\Sample\Calculator\*.*"

I won’t go through the ASPX code line-by-line, but here’s what it looks like after I filled in some values:

NewServiceAppDialog

Simply click the “OK” button to create and deploy the new service application to the farm:

NewServiceAppResult

Note that SharePoint automatically provides the Delete, Publish, and Permissions pages. So administrators can publish your service application to remote server farms and manage who can access it—without any further coding!

So there you have it—a deployment experience that fits our custom Calculator service neatly into the SharePoint Central Administration site just like the built-in services that ship with the product. Now SharePoint administrators will feel right at home creating and deploying our Calculator service in their server farms.

No doubt some of you are noting that a few of the ribbon buttons are still disabled, namely, the Manage, Administrators, and Properties buttons. Never fear—we’ll fix that soon.