Tuesday, 24 November 2009

Create Custom Service Application in SharePoint 2010 – Part 3

Creating Service Administration Pages

We will need to create two administration pages for our Hello service application. One will be used for service application instance creation, and the other one for managing existing application instance. As a reference we will use the pages for Excel Service, which can be located in the ADMIN folder (C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\ADMIN).

First we will create a mapped folder to the ADMIN folder and this is the folder where the administration pages will deploy. To do this, right click the HelloServiceApplication > Add > SharePoint Mapped Folder..., then expand the “TEMPLATE” node and select the “ADMIN” folder. A new ADMIN folder “HelloServiceApplication” will be added to the project.

We will first create the service creation page. Right click the newly created “HelloServiceApplication” folder under “ADMIN” and add a new item. Select the “Application Page” template and give the page a name “CreateApplication.aspx”

I am using Visual Studio 2010 Beta 2 and notice a funny thing. VS2010 will create the CreateApplication.aspx in “Layouts/HelloServiceAplication”. If you have this problem, just drag the aspx file to ADMIN\HelloServiceApplication, as this is the location where we referencing in out Service Application implementation (HelloService.cs).

Put the following code in the aspx file:

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CreateApplication.aspx.cs" Inherits="SharePointEgg.Layouts.HelloServiceApplication.CreateApplication" MasterPageFile="~/_layouts/dialog.master" %>

<%@ Assembly Name="Microsoft.Office.Excel.Server.MossHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Register TagPrefix="wssuc" TagName="LinksTable" src="/_controltemplates/LinksTable.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="LinkSection" src="/_controltemplates/LinkSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="/_controltemplates/ButtonSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ActionBar" src="/_controltemplates/ActionBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="/_controltemplates/ToolBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="/_controltemplates/ToolBarButton.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="/_controltemplates/Welcome.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="IisWebServiceApplicationPoolSection" src="~/_admin/IisWebServiceApplicationPoolSection.ascx" %>

<asp:Content ID="Content1" contentplaceholderid="PlaceHolderDialogHeaderPageTitle" runat="server">
    <asp:Literal ID="CreateASAppTitle" Text="Create New Hello Service Application" runat="server"/>
</asp:Content>

<asp:Content ID="Content2" contentplaceholderid="PlaceHolderDialogDescription" runat="server">
    <asp:Literal ID="CreateASAppDesc" Text="Specify the name, application pool, and default for this Application." runat="server"/>
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderId="PlaceHolderDialogBodyMainSection" runat="server">    
    <TABLE border="0" cellspacing="0" cellpadding="0" width="100%" class="ms-authoringcontrols">
        <wssuc:InputFormSection
            Title="<%$Resources:xlsrv, ManagementUI_ServiceAppNameLabel%>"
            runat="server">
            <Template_InputFormControls>
                <wssuc:InputFormControl LabelText="" LabelAssociatedControlID="TextBoxAppName" runat="server">
                    <Template_control>
                        <wssawc:InputFormTextBox title="<%$Resources:xlsrv, ManagementUI_ServiceAppNameLabel%>" class="ms-input" ID="TextBoxAppName" Columns="35" Runat="server" MaxLength=256 />
                        <wssawc:InputFormRequiredFieldValidator ID="AppNameValidator"
                            ControlToValidate="TextBoxAppName"
                            ErrorMessage="<%$Resources:xlsrv, ManagementUI_RequiredFieldErrorMessage%>"
                            width='300px'
                            Runat="server"/>
                        <wssawc:InputFormCustomValidator ID="UniqueNameValidator"
                            ControlToValidate="TextBoxAppName"
                            ErrorMessage="<%$Resources:xlsrv, ManagementUI_DuplicateNameErrorMessage%>"
                            OnServerValidate="ValidateUniqueName"
                            runat="server" />
                    </Template_control>
                </wssuc:InputFormControl>
            </Template_InputFormControls>
        </wssuc:InputFormSection>

        <wssuc:IisWebServiceApplicationPoolSection id="AppPoolSection" runat="server" />

        <wssuc:InputFormSection
            Title="<%$Resources:xlsrv, ManagementUI_DefaultLabel%>"
            Description="<%$Resources:xlsrv, ManagementUI_DefaultDescription%>"
            runat="server">
            <Template_InputFormControls>
                <wssuc:InputFormControl LabelText="" LabelAssociatedControlID="CheckBoxDefault" runat="server">
                    <Template_control>
                        <asp:CheckBox Checked="True" ID="CheckBoxDefault" Text="<%$Resources:xlsrv, ManagementUI_DefaultCheckboxDescription%>" Runat="server" />
                    </Template_control>
                </wssuc:InputFormControl>
            </Template_InputFormControls>
        </wssuc:InputFormSection>

        <SharePoint:FormDigest ID="FormDigest1" runat=server/>        
</asp:Content>

Put enter the following for the code behind:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Administration;
using Microsoft.Office.Server.Internal.UI;

namespace SharePointEgg.Layouts.HelloServiceApplication
{
    public partial class CreateApplication : GlobalAdminPageBase
    {
        // Fields
        protected RequiredFieldValidator AppNameValidator;
        protected IisWebServiceApplicationPoolSection AppPoolSection;
        protected Button ButtonOk;
        protected CheckBox CheckBoxDefault;
        protected TextBox TextBoxAppName;
        protected CustomValidator UniqueNameValidator;

        // Methods
        protected void OkButton_Click(object sender, EventArgs e)
        {
            //ULS.SendTraceTag(0x39766663, UlsInformation.Management, ULSTraceLevel.Verbose, "ExcelServerCreateApplication.ButtonNext_Click: Entering ButtonNext_Click...");
            this.Page.Validate();
            if (this.Page.IsValid)
            {
                string applicationName = this.TextBoxAppName.Text.Trim();
                SPLongOperation.Begin(delegate(SPLongOperation longOperation)
                {
                    try
                    {
                        SharePointEgg.HelloServiceApplication serviceApplication = null;
                        serviceApplication = HelloService.Local.CreateApplication(applicationName, this.AppPoolSection.GetOrCreateApplicationPool());
                        serviceApplication.Update();
                        IAsyncResult asyncResult = serviceApplication.BeginProvision(null, null);
                        serviceApplication.EndProvision(asyncResult);
                        HelloServiceApplicationProxy proxy = HelloService.Local.CreateProxy(applicationName, serviceApplication);
                        proxy.Update();
                        proxy.AddToDefaultGroup(this.CheckBoxDefault.Checked);
                    }
                    catch (Exception exception)
                    {
                        //ULS.SendTraceTag(0x39766664, UlsInformation.Management, ULSTraceLevel.High, "ExcelServerCreateApplication.ButtonNext_Click: {0}", new object[] { exception.Message });
                        throw;
                    }
                    longOperation.EndScript("window.frameElement.commonModalDialogClose(1, null);");
                });
            }
            //ULS.SendTraceTag(0x39766665, UlsInformation.Management, ULSTraceLevel.Verbose, "ExcelServerCreateApplication.ButtonNext_Click: Exiting ButtonNext_Click.");
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            ((DialogMaster)this.Page.Master).OkButton.Click += new EventHandler(this.OkButton_Click);
        }

        protected void ValidateUniqueName(object sender, ServerValidateEventArgs eventArgs)
        {
            if (eventArgs == null)
            {
                throw new ArgumentNullException("eventArgs");
            }
            SharePointEgg.HelloServiceApplication applicationByName = SharePointEgg.HelloServiceApplication.GetApplicationByName(this.TextBoxAppName.Text.Trim());
            eventArgs.IsValid = applicationByName == null;
        }
    }
}

We also need to remove some of the control’s declaration in teh designer.cs because we declared these controls in the code behind. CreateApplication.aspx.designer.cs looks like this:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace SharePointEgg.Layouts.HelloServiceApplication {


    public partial class CreateApplication
    {

        /// <summary>
        /// CreateASAppTitle control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::System.Web.UI.WebControls.Literal CreateASAppTitle;

        /// <summary>
        /// CreateASAppDesc control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::System.Web.UI.WebControls.Literal CreateASAppDesc;

        /// <summary>
        /// FormDigest1 control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::Microsoft.SharePoint.WebControls.FormDigest FormDigest1;
    }
}

Next we will create a management page for our service application. For demonstration purpose, this page is just a dummy page that doesn’t do anything. Right click the newly created “HelloServiceApplication” folder under “ADMIN” and add a new item. Select the “Application Page” template and this time we call it “HelloServiceAdmin.aspx”. Due to the bug that I mentioned above, you may need to drag the page from “Layouts/HelloServiceApplication” back to “ADMIN\HelloServiceApplication”.

Put the following code in the aspx file:

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="HelloServiceAdmin.aspx.cs" Inherits="SharePointEgg.Layouts.HelloServiceApplication.HelloServiceAdmin" MasterPageFile="~/_admin/admin.master" %>

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages.Administration, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="LinksTable" src="/_controltemplates/LinksTable.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="LinkSection" src="/_controltemplates/LinkSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="/_controltemplates/ButtonSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ActionBar" src="/_controltemplates/ActionBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="/_controltemplates/ToolBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="/_controltemplates/ToolBarButton.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="/_controltemplates/Welcome.ascx" %>

<asp:Content ID="Content1" contentplaceholderid="PlaceHolderPageTitle" runat="server">
    Test Service Admin Page
</asp:content>

<asp:Content ID="Content2" contentplaceholderid="PlaceHolderPageTitleInTitleArea" runat="server">
    Test Service Admin Page
</asp:Content>

<asp:content ID="Content3" contentplaceholderid="PlaceHolderPageDescription" runat="server">
    <asp:Literal ID="m_pathPageDescription" runat="server" />
</asp:content>

<asp:content ID="Content4" contentplaceholderid="PlaceHolderMain" runat="server">
    <table width="100%" class="propertysheet" cellspacing="0" cellpadding="0" border="0"> <tr> <td class="ms-descriptionText"> <asp:Label ID="LabelMessage" Runat="server" EnableViewState="False" class="ms-descriptionText"/> </td> </tr> <tr> <td class="ms-error"><asp:Label ID="LabelErrorMessage" Runat="server" EnableViewState="False" /></td> </tr> <tr> <td class="ms-descriptionText"> <asp:ValidationSummary ID="ValSummary" HeaderText="<%$SPHtmlEncodedResources:spadmin, ValidationSummaryHeaderText%>" DisplayMode="BulletList" ShowSummary="True" runat="server"> </asp:ValidationSummary> </td> </tr> </table>

    <P>
        <span style="font-size:140%"><asp:HyperLink ID="m_linkToSettingsPage" runat="server"/></span><br />
        Test Service Admin Page Description
    </P>
    <P>
        <span style="font-size:140%"><asp:HyperLink ID="m_linkToTrustedLocations" runat="server"/></span><br />
        Trusted Location
    </P>
    <P>
        <span style="font-size:140%"><asp:HyperLink ID="m_linkToTrustedDataProviders" runat="server"/></span><br />
        Trusted Data Provider
    </P>
    <P>
        <span style="font-size:140%"><asp:HyperLink ID="m_linkToTrustedDcls" runat="server"/></span><br />
        Trusted Data Connection Library
    </P>
    <P>
        <span style="font-size:140%"><asp:HyperLink ID="m_linkToUdfs" runat="server"/></span><br />
        User Defined Library
    </P>

</asp:content>

The code behind looks like this:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.Office.Server.Internal.UI;

namespace SharePointEgg.Layouts.HelloServiceApplication
{
    public partial class HelloServiceAdmin : GlobalAdminPageBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

And we have completed the administration pages for our service application. However we are not there yet. We need to add our service application to the server farm so that we can create an instance of it. We will create a feature receiver to do this.

Feature Receiver: Create Service Application in Server Farm

Right click the “Features” folder in Visual Studio and select “Add Feature”. Then right click the newly created Feature1.feature node and select “Add Event Receiver”. This will create the event receiver class and xml file.

Open “Feature1.EventReceiver.cs” and uncomment the “FeatureActivated” method. Put the following code in the method:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                HelloService service = HelloService.Local;
                if (service == null)
                {
                    service = new HelloService(SPFarm.Local);
                    service.Update();
                }
            }
            catch
            {
            }
        }

And there you go our service application is ready to be deployed!!!

Creating Client Page

Ops, just before we deploy, we will quickly create a page under the “Layouts\HelloServiceApplication” folder to call our service application. Right click the “Layouts” folder in Visual Studio and add a new item. Select “Application Page” and we will call this page “Test.aspx”. We will have a textbox, a button, and a literal control in our page. When the user clicks the button, the page will grab whatever value in the textbox and sends it to our custom service application. The literal control will be used to display whatever returns from the service application. We will insert the controls into “PlaceHolderMain” like this:

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <asp:UpdatePanel runat="server">
        <ContentTemplate>
            <SharePoint:InputFormTextBox ID="InputFormTextBoxName" runat="server" />
            <asp:Button runat="server" ID="OKButton" Text="Submit" />
            <asp:Literal runat="server" ID="ResultLiteral"></asp:Literal>
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>

The code behind is here:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace SharePointEgg.Layouts.HelloServiceApplication
{
    public partial class Test : LayoutsPageBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            OKButton.Click += new EventHandler(OKButton_Click);
        }

        void OKButton_Click(object sender, EventArgs e)
        {
            try
            {
                HelloServiceApplicationProxy proxy = HelloServiceApplicationProxy.GetProxy(SPServiceContext.Current);
                SharePointEgg.HelloServiceApplication app = proxy.Application;
                string endPoint = app.DefaultEndpoint.ToString();

                ResultLiteral.Text = app.Hello(InputFormTextBoxName.Text);

            }
            catch (Exception ex)
            {
                ResultLiteral.Text = ex.Message;
            }
        }
    }
}
Deploying Service Application

Right click the “HelloServiceApplication” and select “Deploy”. Hopefully you will get a “Deploy succeeded” message.

Create an Hello Service Application Instance

First browse to the SharePoint Central Administration site. Under “Application Management”, click “Manage service applications”. In the “Manage Service Applications” page, click the “New” button in the ribbon and you should see “Hello Service Application” in there":

Select “Hello Service Application” and a dialog should show up. This is actually the CreateApplication.aspx that we created earlier.

Give it a name “Hello Service Application”. Create a new application pool called “HelloServiceAppPool”. Check the Default checkbox as well. Click “OK” to proceed.

After a while, a new service application called “Hello Service Application” is created, like shown as below.

Select the new service application and click “Manage” in the Ribbon. You will be redirect to the management page, however this page doesn’t really do anything now.

Now let see if everything is working by accessing the client page that we created. You can get to the client page via the URL http://yoursite/_layouts/HelloServiceApplication/Test.aspx

Enter a name say “Wilson” in the text box and click OK. If anything is working, you will get and message back like the following screen:

1 comment:

Sean said...

Thanks, Wilson! I've been trying to stand up a service application proof of concept for the past few days. I've been trying some examples from some SharePoint heavyweights, but their examples are either buggy or incomplete. Yours works perfectly, and sticks to the core working parts required for a proper Service Application without getting lost in the weeds of PowerShell, database creation, etc.

You've provided an excellent working example of a tough new concept in an area where there are few good resources. Kudos! Have a couple of beers and send me the bill.