Thursday, November 15, 2007

Create Custom BlackPearl SmartObject Service

1) Introduction

The purpose of this article is to present the basic steps that are needed to create a custom service for BlackPearl SmartObjects. This article will give the details of how to quickly create and update a SmartObject Service. Then I will show you how to create a SmartObject that uses the custom service you have authored.

This article will not dive into the details for best practices on to build SOA compliant services. I highly recommend reading going to the Microsoft Patterns & Practices Website and review their patterns for web services. SmartObject Services are not web services however the architecture used to build one is comparable.

I would also like to thank Codi at K2 for providing me an early version of the 201 training materials (currently under construction) that allowed me to spin up on this. Much of the content of this article is information that was derived from that training module.

2) When Do You Need to Write a Custom SmartObject Service

You will want to create a custom SmartObject Service when you want to read in data from any existing custom or vendor database. K2 provides you some SmartObject Services:

  • SmartBox (cannot be used for custom SQL Databases)
  • SharePoint
  • Active Directory
  • K2 [blackpearl]
  • Salesforce.com
  • K2 201 Training Materials and SDK show how to build a DynamicSQL service that can hook into any SQL Database. It is built to dynamically define its interface by reading the table schema and building an interface around it.

K2 plans to build more SmartObject Services to hook into other enterprise products

3) Summary Steps to Create

These are the summary steps to create a SmartObject Service:

  • Create a Service Broker class.
  • Override all of the required methods and define the SmartObject Service interface in this class.
  • Build the service and deploy the dll.
  • Register the SmartObject service.
  • Create a Service Instance of the SmartObject service in the K2 Workspace.
  • Use a method of the SmartObject Service in a SmartObject.

4) Summary Steps to Update

These are the summary steps to update a SmartObject Service:

  • Update the service.
  • Stop the K2 BlackPearl Service on the server.
  • Replace the dll.
  • Restart the K2 BlackPearl Service on the server.
  • Refresh all service instances created within the K2 Workspace.

5) Detailed Steps to Create

5.1) Create Class Library

Create a standard class library. You will need to add a reference to SourceCode.SmartObjects.Services.ServiceSDK which is located at \\Program Files\K2 blackpearl\Host Server\Bin\. It is suggested that you change the Copy Local property of the reference to False.

Add the following using statements:

using SourceCode.SmartObjects.Services.ServiceSDK;

using SourceCode.SmartObjects.Services.ServiceSDK.Objects;

using SourceCode.SmartObjects.Services.ServiceSDK.Types;

5.2) Create SmartObject Service

Create class that inherits from ServiceAssemblyBase. Note that I tried creating more than one class that inherits from ServiceAssemblyBase in the same class library and the second one would never be recognized. If you want to create another SmartObject Service class, you will have to create a new class library project. I create class called MyCustServiceBroker.


public class MyCustServiceBroker : ServiceAssemblyBase
{
public MyCustServiceBroker()
{

}
}

You need to override the following methods GetConfigSection(), DescribeSchema(), Execute() and Extend().

5.2.1) () is a method used to define configuration values for the service instance. If you service is going to make a database connection or needs a URL to make a connection to another web service, you will add a configuration here. An administrator will enter the value within the K2 Workspace.

All you need to do is add values in a Key/Value pair fashion; similar to creating a config file.


public override string GetConfigSection()
{
this.Service.ServiceConfiguration.Add("Connection", true, "Default Value");

return base.GetConfigSection();
}

5.2.2) DescribeSchema()

DescribeSchema() is the method that is used to define the interface for the SmartObject service. The values that you set within this interface will dictate how developers will hook their SmartObjects into your SmartObject Service.

First you will define the service. This information will be presented to the developer when they are selecting a service to use with a SmartObject. Second, you will create a ServiceObject. This broker class can have one to many ServiceObjects. I tend to think of ServiceObjects as class definition. Third, each ServiceObject will have properties which are used to pass values in and out of methods. Fourth, you will need to add methods to your service and in this example I created a Load method. There is a finite set of methods you can create: Create, Delete, Execute, List, Read and Update. Take special note in how I add the properties to the Validation, Input and Output collections. A developer will map the fields of their SmartObject to the method properties of a ServiceObject.

Side note you can possibly use the Execute for custom method(s) you want to write for the service. You can create an enumeration property where each enumeration value will map to each custom method that you have. Then you could have an XML property to pass custom parameter(s) for the specific enumeration. I would need to spend a little time testing this out but it should work.

public override string DescribeSchema()
{
//set base info
this.Service.Name = "MyCustomService";
this.Service.MetaData.DisplayName = "My Custom Service";
this.Service.MetaData.Description = "The simple little service.";

//Create the service object, one to many
ServiceObject so = new ServiceObject();
so.Name = " MyCustomServiceObject ";
so.MetaData.DisplayName = "My Test Service";
so.MetaData.DisplayName = "Use for my test service.";
so.Active = true;

//Create field definition
Property property1 = new Property();
property1.Name = "MyField1";
property1.MetaData.DisplayName = "My Field 1";
property1.MetaData.Description = "My Field 1";
property1.Type = "System.String";
property1.SoType = SoType.Text;
so.Properties.Add(property1);

//Create field definition
Property property2 = new Property();
property2.Name = "MyField2";
property2.MetaData.DisplayName = "My Field 2";
property2.MetaData.Description = "My Field 2";
property2.Type = "Integer";
property2.SoType = SoType.Number;
so.Properties.Add(property2);

//Create method
Method method = new Method();
method.Name = "Load";
method.MetaData.DisplayName = "Load";
method.MetaData.Description = "Load custom service data";
method.Type = MethodType.Read;
method.Validation.RequiredProperties.Add(property1);
method.InputProperties.Add(property1);
method.ReturnProperties.Add(property1);
method.ReturnProperties.Add(property2);
so.Methods.Add(method);

this.Service.ServiceObjects.Add(so);

return base.DescribeSchema();
}

5.2.3) Execute()

Execute() is the method that is used persist data.

Note that you should add errors to the ServicePackage object. This will surface up error messages in a stand way to all callers who are executing the custom ServiceObject method through a SmartObject.


public override void Execute()
{
EventLog log = new EventLog("Application", "localhost", "K2 BlackPearl Server");

try
{
foreach (ServiceObject so in Service.ServiceObjects)
{
switch (so.Name)
{
case "MyCustomServiceObject ":
ExecuteCustomService(so);
break;

default:
throw new Exception("Service Object Not Found");
}
}
}
catch (Exception ex)
{
string errorMsg = Service.MetaData.DisplayName + " Error >> " + ex.Message;
log.WriteEntry(errorMsg);
ServicePackage.ServiceMessages.Add(errorMsg, MessageSeverity.Error);
ServicePackage.IsSuccessful = false;
}
}

private void ExecuteCustomService(ServiceObject so)
{
foreach (Method method in so.Methods)
{
switch (method.Type)
{
case MethodType.Read:
ReadCustomService(so, method);
break;

default:
throw new Exception("Service method undefined");
}
}
}

//This method shows how return a single row of data, this would
//be used for Read and Create methods (when calling Create you will
//want to return primary key of new record)
private void ReadCustomService(ServiceObject so, Method method)
{
//Add in code here to retrieve data from any external data source and
//load it into the result set for this method.
so.Properties.InitResultTable();
so.Properties["MyField1 "].Value = "Value 1";
so.Properties["MyField2"].Value = "Value 2";
so.Properties.BindPropertiesToResultTable();
}


//This method shows how return a collection of data using a DataTable.
//This would be used for a List method
private void ReadCollectionCustomService(ServiceObject so, Method method)
{
//Add in code here to retrieve data from any external data source and
//load it into the result set for this method.
so.Properties.InitResultTable();
DataTable resultTable = this.ServicePackage.ResultTable;
}

5.2.4) Extend()

Unfortunately I do not have much information Extend() but it is not needed for this service.


public override void Extend()
{
//throw new Exception("The method or operation is not implemented.");
}

Note that are several other methods that you may need to override later. For instance there is a Rollback method that should be used when the SmartObject method transaction has been configured to rollback changes if the transaction should fail.

5.3) Build and Deploy It

Build the library. Then get the dll and place it in \\Program Files\K2 blackpearl\ServiceBroker.

5.4) Register the SmartObject Service

There is an executable in \\Program Files\K2 blackpearl\ServiceBroker called BrokerManagement.exe. Double click on it and then click on Configure Services, then right click the services node and select Register New Service Type. Fill in all of the required information and find the class library dll that was placed in \\Program Files\K2 blackpearl\ServiceBroker. Press ok and your SmartObject Service is now available for use.

5.5) Create a Service Instance

Now you need to create an instance of the SmartObject Service you have defined. Instances of your SmartObject are instantiated through the K2 BlackPearl Workspace. If you wrote a very generic service you can create multiple service instances and use your configurations to make connections to different data sources.

Open the K2 BlackPearl Workspace >> go the Management Console >> select the BlackPearl Server >> SmartObjects >> Services >> My Custom Service

On this screen, press the add button and fill in all configuration information that is required as part of the GetConfigSection() method.

5.6) Use the Service Method in a SmartObject

The next step is to start using the SmartObject Service in the methods of your service just like you would with a service that comes with BlackPearl (ex. SmartBox, Active Directory).

6) Detailed Steps to Update

6.1) Update the Service

Make any modifications you need to your service.
6.2) Stop the K2 BlackPearl Service

Go to Start >> Administrative Tools >> Services >>K2 [BlackPeal] Server. Stop the service.
6.3) Replace the SmartObject Servuce dll

Drop in the new dll overriding the old dll in \\Program Files\K2 blackpearl\ServiceBroker.

6.4) Restart the K2 BlackPearl Service

If you have not closed the Service Console, simply restart the service.

6.5) Refresh Service Instances

Start the BrokerManagement.exe executable in \\Program Files\K2 blackpearl\ServiceBroker. Right click the service node and select Refresh All Service Instances.

7) Create a SmartObject using the Custom SmartObject Service

Create a SmartObject Project or add a new SmartObject to an existing project. Click on the Advanced Mode button at the top of the screen. This is important because this SmartObject will have fields that are not in the SmartBox database. When adding new fields; make sure to uncheck the SmartBox column. Simply add two new fields called MyField1 (string) and MyField2 (integer) and uncheck the SmartBox. Now remove all methods from the bottom except for the Load. Select it and press the Edit button. Run this wizard in advanced mode and select Next.

On the Method Details page make sure the type is Read and the Transaction is Continue.

Click Next Twice and on the Service Objects Methods screen remove the SmartBox Service, we will not use it. Press the Add button. Press the ellipse button and in the tree select ServiceObject Server(s) >> Service Object Server >> My Custom Service >> My Test Service >> Load. That will load in all of the Input and Return properties that were defined earlier. Now assign those properties to Fields on the SmartObject and you are done!

Now you can start using your SmartObject that is Loading from a custom data source.