Showing posts with label K2 SmartObjects. Show all posts
Showing posts with label K2 SmartObjects. Show all posts

Wednesday, April 15, 2009

K2 blackpearl Developer Resources

I was just given some links to a really great resource for K2 Developers. Chris Geier at K2 has pulled this together and is managing it; this is great!

In it are good blog postings, K2 whitepapers, KB articles, MSDN articles, videos, etc. I still recommend getting the K2 blackpearl Professional book and use this information to supplement your learning of the K2 blackpearl Platform.

Saturday, September 6, 2008

Creating Custom SmartObjects Services Part 2

Introduction


This is a second part to the first article I wrote some time ago on how to create a custom SmartObject Service and how to deploy it. It was completely based off Dynamic SQL Service located in the K2 blackmarket. The thing was the service was geared completely towards generating the interface based on the schema of a SQL Database. So I was lead down the path that I had to follow this pattern to define my SmartObject Service. Well I was playing around the other day and I saw a different project on the K2 blackmarket called the AD Interop. I read the code and I was flabbergasted. Apparently it is possible to create a regular class and then decorate the class, properties and methods with attributes to describe it. I dropped Colin an email saying to check this out and we both agreed this is a much cleaner way of building SmartObject Services which are not being generated dynamically.

I read through the AD Interop project and there were a few things which the project owner did not finish out like how to handle exceptions and how to properly pass configuration values from the ServiceBroker to the SmartObject Service. Below is a proof of concept I created for an employee SmartObject Service. Now it is not the most verbose implementation but should lead you in the right direction when trying to create a SmartObject Service.

Employee Object

First I actually went back to my first article and following my instructions for create a project. Then I created an Employee object. Notice it does not inherit from anything special. Notice the SourceCode.SmartObjects.Services.ServiceSDK.Objects.ServiceObject attribute which has been added to the class. It describes the Employee object.

using System;
using System.Collections.Generic;
using System.Text;
using SourceCode.SmartObjects.Services.ServiceSDK.Attributes;
using SourceCode.SmartObjects.Services.ServiceSDK.Objects;
using SourceCode.SmartObjects.Services.ServiceSDK.Types;

namespace CustomTestService
{

[ServiceObject("EmployeeServiceObject",
"Employee Service Object",
"This is a test Employee Service Object.")]
class Employee
{

}
}

Next I created some attributes and properties for the Employee object. Again nothing special. All of the properties are decorated with SourceCode.SmartObjects.Services.ServiceSDK.Objects.Property which describes name of the property, the type, a display name for the property and the description. The only property of any special interest is the DatabaseConnection property. This will only be used by the service broker. The service broker will set the database connection value that was set in the K2 workspace. Note that properties that are not exposed through a method are not visible in a SmartObject definition and cannot be set. This was a concern of mine because I did not want to expose this property publically.

        #region attributes

private string _databaseConnection = "";

private int _employeeNumber;
private string _department = "";
private string _firstName = "";
private string _lastName = "";
private string _email = "";

#endregion


#region properties

[Property("EmployeeNumber", SoType.Number,

"Employee Number", "Employee Number of employee")]

public int EmployeeNumber {

get {

return _employeeNumber;

}

set {

_employeeNumber = value;

}

}



[Property("Department", SoType.Text,

"Department", "Department of employee")]

public string Department

{

get

{

return _department;

}

set

{

_department = value;

}

}



[Property("FirstName", SoType.Text,

"First Name", "First Name of employee")]

public string FirstName {

get {

return _firstName;

}

set {

_firstName = value;

}

}



[Property("LastName", SoType.Text,

"Last Name", "Last Name of employee")]

public string LastName {

get {

return _lastName;

}

set {

_lastName = value;

}

}



[Property("Email", SoType.Text,

"Email", "Email of employee")]

public string Email {

get {

return _email;

}

set {

_email = value;

}

}



/// <summary>

/// Note this property was created to pass a value from the

/// EmployeeServiceBroker to the Employee object. This property

/// is not exposed through any of the methods.

/// </summary>

[Property("DatabaseConnection", SoType.Text,

"Database Connection", "Connection string to the database.")]

public string DatabaseConnection

{

get

{

return _databaseConnection;

}

set

{

_databaseConnection = value;

}

}

#endregion

Finally I created three methods: GetEmployee, HireEmployee, and GetEmployees. These are stubs and with no real implementation. Notice that each method is decorated with SourceCode.SmartObjects.Services.ServiceSDK.Objects.Method which takes the name of the method, the method type, a display name, and description. As well, there are three arrays which define the required fields, input fields and output fields. Notice the methods always return itself and any property that is defined as an input field to the method will have a value populated into it which can be used to perform operations (like searching for an employee based on their employee number). I was really happy to see I was able to easily return a collection of Employee objects for a List operation.


        #region Methods



/// <summary>

/// This method will get an Employee based on the Employee Number.

/// The Employee number is required.

/// </summary>

/// <returns></returns>

[Method("GetEmployee", MethodType.Read, "Get Employee",

"Method will get Employee based on Employee ID.",

new string[] {"EmployeeNumber"},

new string[] {"EmployeeNumber"},

new string[] {"FirstName", "LastName", "Email", "Department"})]

public Employee GetEmployee()

{

try

{

///Write code here to fill in the object from where ever.

///this.EmployeeNumber; will contain the employee number

///value that was sent by the caller.



///Filling in some test data.

FirstName = "Jason";

LastName = "Apergis";

Department = "Professional Services";

Email = "jason@foobar.com";

}

catch (Exception ex)

{

throw new Exception("Error Getting an Employee >> " + ex.Message);

}



return this; //return the Employee object to the Execute() call

}



/// <summary>

/// This method will create an Employee. First name, last name

/// and department are required. An employee number and email

/// address will be generated as a result of the creation of the employee.

/// These two values will be returned.

/// </summary>

/// <returns></returns>

[Method("HireEmployee", MethodType.Execute, "Hire Employee",

"Method will complete the hire of an email creating a Employee Number and Email Address.",

new string[] {"FirstName", "LastName", "Department"},

new string[] {"FirstName", "LastName", "Department"},

new string[] {"EmployeeNumber", "Email"})]

public Employee HireEmployee()

{

Employee employee = new Employee();



try {

///Write code here to save the object to where ever.

///this.FirstName, etc. to get values provided by the caller.



///returning some test values

EmployeeNumber = 1;

Email = "jason@foobar.com";

}

catch (Exception ex)

{

throw new Exception("Error Hiring an Employee >> " + ex.Message);

}



return this; //return the Employee object to the Execute() call

}



/// <summary>

/// This method will get a list of employees. Providing no value

/// will get all employees. Providing a Department name will get

/// all employees for the department. This is because the Department

/// field is not required.

/// </summary>

/// <returns></returns>

[Method("GetEmployees", MethodType.List, "Get Employees",

"Method will get Employees.",

new string[] { },

new string[] {"Department" },

new string[] {"EmployeeNumber", "FirstName", "LastName", "Email" })]

public List<Employee> GetEmployees()

{

List<Employee> employees = new List<Employee>();



try

{

///Check of there is a Department value

if (String.IsNullOrEmpty(Department))

{

///Get all Employees

}

else {

///Get Employees based on department id

}



///Filling in some test data.

Employee emp1 = new Employee();

emp1.EmployeeNumber = 0;

emp1.FirstName = "Jason";

emp1.LastName = "Apergis";

emp1.Department = "Professional Services";

emp1.Email = "jason@foobar.com";



employees.Add(emp1);



Employee emp2 = new Employee();

emp2.EmployeeNumber = 1;

emp2.FirstName = "Ethan";

emp2.LastName = "Apergis";

emp2.Department = "Professional Services";

emp2.Email = "ethan@foobar.com";



employees.Add(emp2);

}

catch (Exception ex)

{

throw new Exception("Error Getting Employees >> " + ex.Message);

}



return employees; //return the Employee objects to the Execute() call

}



#endregion

Now below is the EmployeeServiceBroker which inherits from ServiceAssemblyBase. In the GetConfigSection() override is where the DatabaseConnection service configuration is defined. When the service instance is created in the K2 Workspace, the administrator will be required to set a connection string which will be associated to the specific service instance. In the DescribeSchema() method notice that the Employee object is add the Service's SmartObjects list. Unlike my first article, nothing more needs to be done in this method because the definition is on the Employee object. Finally I had to override the Execute() method. The Execute() method actually does not have to be overridden, however I am overriding it so that I can pass the database connection string for the SmartObject service instance to the Employee object. As well, I wanted to properly send exception messages using the ServicePackage.

using System;

using System.Collections.Generic;

using System.Text;

using SourceCode.SmartObjects.Services.ServiceSDK;

using SourceCode.SmartObjects.Services.ServiceSDK.Objects;

using SourceCode.SmartObjects.Services.ServiceSDK.Types;

using System.Data;

using System.Data.SqlClient;



namespace CustomTestService

{

public class EmployeeServiceBroker : ServiceAssemblyBase

{

public EmployeeServiceBroker()

{



}



public override string GetConfigSection()

{

this.Service.ServiceConfiguration.Add("DatabaseConnection", true, "Default Value");



return base.GetConfigSection();

}



public override string DescribeSchema()

{

//Custom service object

Type employeeType = typeof(Employee);

base.Service.ServiceObjects.Add(new ServiceObject(employeeType));



return base.DescribeSchema();

}



/// <summary>

/// This method does not have to be implemented. However it is used to set

/// configuration values that are set in the K2 Workspace. As well, exceptions

/// from the Employee object are handled here.

/// </summary>

public override void Execute()

{

try

{

foreach (ServiceObject so in base.Service.ServiceObjects)

{

if (so.Name == "EmployeeServiceObject")

{

string server = base.Service.ServiceConfiguration["DatabaseConnection"].ToString();

so.Properties["DatabaseConnection"].Value = server;

}

}



base.Execute();

}

catch (Exception ex) {

string errorMsg = Service.MetaData.DisplayName + " Error >> " + ex.Message;

this.ServicePackage.ServiceMessages.Add(errorMsg, MessageSeverity.Error);

this.ServicePackage.IsSuccessful = false;

}

}



public override void Extend()

{

//throw new Exception("The method or operation is not implemented.");

}



}

}

Next all I needed to do was deploy the SmartObject service which I describe how to do here.


Employee SmartObject

Now that I have my Employee SmartObject Service deployed, I needed to create an Employee SmartObject. The following is a screenshot of a quick SmartObject that I threw together.


Test Stub

Finally I created a little command line application to test the SmartObject and Service that I had created. I added references to SourceCode.SmartObjects.Client and SourceCode.Hosting.Client and I was off and running.

using System;

using System.Collections.Generic;

using System.Text;

using SourceCode.SmartObjects.Client;

using SourceCode.Hosting.Client.BaseAPI;



namespace TestSO

{

class Program

{

static void Main(string[] args)

{

SmartObjectClientServer server = new SmartObjectClientServer();



try

{

SCConnectionStringBuilder cb = new SCConnectionStringBuilder();

cb.Host = "BLACKPEARL";

cb.Port = 5555;

cb.Integrated = true;

cb.IsPrimaryLogin = true;



//Connect to server

server.CreateConnection();

server.Connection.Open(cb.ToString());



//--------------------------

//Hire the Employee

//Get SmartObject Definition

SmartObject newEmployee = server.GetSmartObject("CustomTestEmployee");



//Hire Employee

newEmployee.Properties["FirstName"].Value = "Jason";

newEmployee.Properties["LastName"].Value = "Apergis";

newEmployee.Properties["Department"].Value = "Professional Services";



//Hire the employee

newEmployee.MethodToExecute = "HireEmployee";

server.ExecuteScalar(newEmployee);



//values returned

System.Diagnostics.Trace.WriteLine(

newEmployee.Properties["ID"].Value);



System.Diagnostics.Trace.WriteLine(

newEmployee.Properties["Email"].Value);



//--------------------------

//Get the Employee

//Get SmartObject Definition

SmartObject employee = server.GetSmartObject("CustomTestEmployee");



//Set properties

employee.Properties["ID"].Value = "1";



//Get the record

employee.MethodToExecute = "GetEmployee";

server.ExecuteScalar(employee);



System.Diagnostics.Trace.WriteLine(

employee.Properties["FirstName"].Value);



System.Diagnostics.Trace.WriteLine(

employee.Properties["LastName"].Value);



System.Diagnostics.Trace.WriteLine(

employee.Properties["Email"].Value);



//--------------------------

//Get employees using a Department Name

//Get SmartObject Definition

employee = server.GetSmartObject("CustomTestEmployee");



//Set properties

employee.Properties["Department"].Value = "Professional Services";



//Get the records

employee.MethodToExecute = "GetEmployees";

SmartObjectList employees = server.ExecuteList(employee);



//Loop over the return employee values

foreach (SmartObject so in employees.SmartObjectsList) {

foreach (SmartProperty property in so.Properties) {

System.Diagnostics.Debug.Write(

property.Name + "=" + property.Value);

}

}

}

catch (Exception ex)

{

throw new Exception("Error Creating Request >> " + ex.Message);

}

}

}

}

Conclusions


I was extremely happy with how clean this was. I was able to create an Employee object, decorate it and then it was deployed as a Service which K2 or other applications across my enterprise can use. The implementation is extremely decoupled and I had to put no thought to it at all. Going down this path, it is possible to re-use Domain Layers that may already be written and expose them to the enterprise on the large.

Friday, April 18, 2008

SmartObject Workshop Thoughts

Background


Colin Murphy, Gabriel Malherbe and I held a workshop on SmartObjects at the K2 Insider Conference this April. It was an interesting discussion because the three of us had some conference calls beforehand where tried to gain a better understanding of how to position SmartObjects in the Enterprise Architecture. I wrote a blog that has not been published which all three of us iterated on before the conference. I made some statements in that article (to invoke debate) where I asserted that SmartObjects could be considered SOA. I got beat up a little by colleagues and now admit that SmartObjects are not SOA even though they adhere to many of the tenants of SOA like autonomy, manageability, discoverable, maintainable, exception handling, scalability, reliability, transactionality, security, logging, buildability, interoperability, testability, etc. Maybe it is low on the maturity scale of SOA (level 2 of 5) and I still debate that sometimes that is good enough for many organizations. It can be challenging to get a company to invest to get to a high maturity level of SOA.


So then what are SmartObjects? Here are some excerpts from the slides that we presented.


Before and Now


In the K2 2003 days we would have to go into the client event after the code was generated and start modifying it to get data from external places. Remember that there is no code generation in BlackPearl; it is declarative.


Now all we do is drag and drop smart object references into the canvas to gain access to data; no code.


K2 [blackpearl] Solution


  • Blackpearl introduces SmartObjects to solve many of these issues.
  • SmartObjects:
    • Are reusable business entities which can be deployed centrally and consumed by non-technical workflow authors
    • Are a storage location for process data (SmartBox)
    • Are a means to quickly access external LOB Data
    • Allow developers to aggregate data from multiple backend systems into a single, composite object
    • Can be created without writing any code
    • Can be accessed outside of the workflow (ADO Provider) and can be reported on

K2 SmartObject in Enterprise Architecture


The following diagram is from a presentation I prepared in November of 2007 for the Texas User Group. At the time I was digging around SmartObjects trying to understand it. From what it looks like my diagram is still spot on.


K2 states that SmartObjects are used to "provision" data into your workflows. K2 uses it as the underpinning to the entire BlackPearl platform. The two things you should know right off are there are SmartObject Services and SmartObjects.

SmartObject Services are:

  • The providers that access line of business and expose the data through a common interface.
  • They expose methods that can be executed; typically CRUD operations.
  • They are registered with the K2 Server and instances of them are created through K2 Workspace.

SmartObjects are:

  • Class definitions that have data elements that map into the methods that are provided by the SmartObject Service.
  • SmartObjects can be graphically used within K2 to access data.
  • SmartObjects have a data provider API which you can easily hook into other tiers of the architecture.

In the end SmartObjects is part of your Data Access layer and they allow you to basically write data providers which are access through SmartObjects.

K2 [blackpearl] Solution


The following are out of the box SmartObject Services that are provided by SharePoint.

  • SmartBox (cannot be used for custom SQL Databases)
  • SharePoint
  • Active Directory – I cannot live without this service when building workflows.
  • K2 [blackpearl] – Provides easy access to all data that is within K2.
  • Salesforce.com

Blackmarket SmartObject Services


The Blackmarket is a K2's version of CodePlex. It is an area where developers can share code with one another - http://k2underground.com/k2/ProjectLanding.aspx

This catalog of services is going grow as more people start to contribute.

Conclusions

The following are my conclusions after the discussion.

  • SmartObjects give you a significant edge in delivering timely solutions with K2. They have provided me the ability to immediately access Active Directory, SQL Databases, etc. We have personally seen that we can now deliver working processes in days instead of weeks because we no longer have to build up custom data access layers and go into the code of K2 and manually access the data. I can use the SmartObjects in a drag and drop fashion in line rules, succeeding rules, preceding rules, emails, destination users/rules, any configuration item in any event wizard, etc. It is ridiculously simple to configure my entire K2 process with data and I have zero custom code to get at the data. Plus I can write C# code in custom event handlers to use SmartObject. The API is really clean, simple and similar to using System.Data.
  • Another great thing about SmartObjects is that if you carefully take the time to build them you will start being able to build catalogs of SmartObjects that business users can use when composing their K2 Processes. They will not be exposed to any of the details associated to knowing where the data comes from.
  • Right now one concern is the ability to place business rules against the SmartObjects methods. For instance if you use the Dynamic SQL Service which build the interface of the service based on the schema of the database there is no way put business rules on the SmartObject to ensure that a value must be greater than X or cannot be null. So if a business user were to use SmartObject Event in their process they could possibly just start setting invalid data.
  • The SmartBox is good for doing early iterations of processes but over the long term you may not want to us them. For instance you have limited control over the schema of the database (because it is generated). As well, you may not want to co-mingle data from different processes in the same database.
  • Do not let you SmartObjects replace your Data Warehouse. With SmartObjects it is possible to perform CRUD operations across line of business data sources. It is very tempting to have a SmartObject read from many different places providing a single view of the data despite where the data is being read from. A simple example would have a SmartObject read from Active Directory and SQL Server. You must take performance into consideration when doing something similar to this. Performance issues associated to reading data will be vetted out in the SmartObject Service implementation however if you read in two large data sources and then join them together in the SmartObject layer you will not have the opportunity to index those joins. It would be better to have the SmartObject access data in the Data Warehouse itself.
  • This comment will be a little at odds with the first but LINQ, Entity and .Net Data Services make it challenging to position SmartObjects. SmartObjects Services may use them to access data and surface it up through a SmartObject for K2. However many developers will be tempted just use LINQ directly inside your custom server events, middle and UI layers. I have not personally messed with LINQ and Entity as much as I should but from the hype it is pretty rich and provide a full Object Query Language and Object Relational Mapping (ORM). The one nice thing about SmartObjects is that they provide you a way to centralize and publish services.

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.