Keeping track of Emails with EWS and K2

I haven’t posted anything in a while as I have been working on another project, which I am hoping to unveil sometime very soon. In the meantime though  I wanted to talk about K2 and Exchange. Now we all know that K2 can talk to exchange and send emails and receive replies back in the form of SmartActions out of the box.

But what if we wanted to keep track of the emails sent from a K2 app then this gets a bit tricky. We could save the message in a database using  a SmartObject Event and then use the email Event to send the email. Which is an ok approach, but I think something could be done better, where we don’t need to have this two step/event approach.

So lets have a think about about what i want the assembly to do?

  1. Send an email
  2. View the mailbox
  3. View an email

We could modify the existing email event to do what I am suggesting below, but that would be a pain as we would need to do it every time we use the email event and would also require the person building the workflow to be able to write code.  With the approach  I am going to go through, it  will allow anyone to be able to build a workflow where it would track what emails are being sent without having to write code and more importantly every app will be able to see it’s own emails it has sent out.

We are going to create a Email Endpoint Assembly that will allow a workflow to send an email and reference a primary key , SN, Process Instance Id or  application type (see framework) and view it’s mailbox by same type of information.

Getting Started

We will need the following

  1. Visual Studio 2015+
  2. Microsoft exchange web service (EWS URL)
  3. Exchange version
  4. UserAccount specifically setup just to be used for K2 mailbox (I normally create a service account, that just has a mailbox)
  5. User Account Email Address
  6. Microsoft.Exchange.Webservices.dll

To do this i need use the assembly Microsoft.Exchange.Webservices.dll which you can get from here .

Once we have the above we can start building the new email endpoint assembly.

EWS Code

To setup the connection to exchange server,  it is important to identify which version of exchange we are talking too.

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);

When we have created an instance of the exchange service, we then give the instance the exchange web service url.

service.Url = new Uri(“Web service”);

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.Credentials = new WebCredentials("Username", "Password");

service.Url = new Uri("Web service");
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "Email Address");

We have now got a connection to exchange server via it’s web service and we can do a number of different things such as

  1. Send Email
  2. View mailbox contents , such as the inbox or sent items
  3. View an email
  4. We can also do things such as create meeting requests

We will look at the basic code for sending an email

Sending an Email

To send an email we need to create an instance of the EmailMessage object and pass in the Exchange Service Object.

EmailMessage email = new EmailMessage(service);

Once we have done that we can access the properties and methods of EmailMessage object.

So we can give are email a subject email.Subject = “Subject”;

We can also give the email body and decide whether we want to send a plain text or a HTML message.

email.Body = new MessageBody(BodyType.HTML, Body);

EmailMessage email = new EmailMessage(service);
email.Subject = "Subject";
email.Body = new MessageBody(BodyType.HTML, Body);

To add recipients (To, Cc, Bcc) we just need to add the following code

  • email.ToRecipients.Add(“address”);
  • email.CcRecipients.Add(“address”);
  • email.BccRecipients.Add(“address”);

If you have more than one email address for ‘To’ or ‘Cc’ or the ‘Bcc’ then we can simply loop through the correct address method parameter. Like in the example below.

 if (To.Contains(";"))
 {
 String[] to = To.Split(';');
 foreach (var address in to)
 {
 email.ToRecipients.Add(address);
 }
 }
 else
 {
 email.ToRecipients.Add(To);
 }

To send the email we simply use .Send(); method

 email.SendAndSaveCopy();

Now we can send a basic email. So let us have a look how we can now extend this email so it can contain some additional properties that relate to the workflow it is being sent from.

The EmailMessage object allows us to add properties called extend properties and they are really simple to create. The only thing you need to remember is that the GUID used to identify the property must be the same every time we an email is sent and needs to be the same for when when we retrieve the mailbox.

So in this example i am going to bind the process instance id to the email message. We will then be able to search the sent items mailbox and retrieve all the messages that relates to that process instance id.

Creating extend properties.

This is the most important part , extend properties is what allows the ability to be able to group emails by the process Instance I’d, business key etc.. 

Create a Guid called ‘ProcessInstanceId’ and assign it a GUID.

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc")

We then have to define the extend property by giving the property a name in this case the property is called ‘ProcessInstanceId’ and we define the data type of the property as a ‘String’.

 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);

 

Now that we have defined the property , we can now populate the email with the process instance id. In code example below I am checking to see if the ‘ProcessInstanceId’ is greater than 0 or is not null and if true it will assign the property the value of the ‘ProcessInstanceId’ and if it is false it will assign the property a 0.

email.SetExtendedProperty(ProcessInstanceId_ExtendedPropertyDefinition, (ProcessInstanceId > 0 | ProcessInstanceId != null ? ProcessInstanceId : 0));

 

Now every time we send an email, it will now contain the process instance id.  In the complete code example of the ‘Send Emall’ method below I have also added some additional properties to contain the following

  1. Primary Key of the main business data
  2. ProcessTypeId (framework see here)
  3. Foilo of the process instance
  4. MessageId, so we can identify each email
public static string SendEmail(string Subject,string Body, string To, string Cc,string Bcc,int importance, string sn,string Folio, int? ProcessInstanceId, string ProcessTypeId, string BusinessKey)
 {
 string result = string.Empty;
 ExchangeService service = ConnectToExchange();
 try
 {
 if (To != null || To.Length != 0)
 {
 EmailMessage email = new EmailMessage(service);
 email.Subject = Subject;
 email.Body = new MessageBody(BodyType.HTML, Body);

Guid SN_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition SN_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(SN_PropertySetId, "SN", MapiPropertyType.String);
 email.SetExtendedProperty(SN_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(sn) ? sn : "0_0"));

Guid Folio_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
ExtendedPropertyDefinition Folio_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(Folio_PropertySetId, "Folio", MapiPropertyType.String);
 email.SetExtendedProperty(Folio_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(Folio) ? Folio : "Email Message"));

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);
 email.SetExtendedProperty(ProcessInstanceId_ExtendedPropertyDefinition, (ProcessInstanceId > 0 | ProcessInstanceId != null ? ProcessInstanceId : 0));

Guid BusinessKey_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition BusinessKey_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(BusinessKey_PropertySetId, "BusinessKey", MapiPropertyType.String);
 email.SetExtendedProperty(BusinessKey_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(BusinessKey) ? BusinessKey : "0"));

Guid ProcessTypeId_PropertySetId = Guid.Parse("d6520129-3c59-4191-b9d7-4f5160329e4f");ExtendedPropertyDefinition ProcessTypeId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessTypeId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);
 email.SetExtendedProperty(ProcessTypeId_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(ProcessTypeId) ? ProcessTypeId : "00000000-0000-0000-0000-000000000000"));

Guid MessageId_PropertySetId = Guid.Parse("6e997d14-d9b3-4516-8d14-0a10b0aa74aa");
 string MessageId = Guid.NewGuid().ToString();
 ExtendedPropertyDefinition MessageId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(MessageId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);
 email.SetExtendedProperty(MessageId_ExtendedPropertyDefinition, MessageId);



if (To.Contains(";"))
 {
 String[] to = To.Split(';');
 foreach (var address in to)
 {
 email.ToRecipients.Add(address);
 }
 }
 else
 {
 email.ToRecipients.Add(To);
 }



if (!string.IsNullOrEmpty(Cc))
 {
 if (Cc.Contains(";"))
 {
 String[] to = Cc.Split(';');
 foreach( var address in to)
 {
 email.CcRecipients.Add(address);
 }
 }
 else
 {
 email.CcRecipients.Add(Cc);

}
 }

if (!string.IsNullOrEmpty(Bcc))
 {
 if (Bcc.Contains(";"))
 {
 String[] to = Bcc.Split(';');
 foreach (var address in to)
 {
 email.BccRecipients.Add(address);
 }
 }
 else
 {
 email.BccRecipients.Add(Cc);

}
 }

if (importance > 0)
 {
 email.Importance = (importance == 1 ? Microsoft.Exchange.WebServices.Data.Importance.Normal : Importance.High);
 }

email.SendAndSaveCopy();

result = email.Id.ToString();
 }
 }
 catch(Exception ex)
 {
 result = "Error: " + ex.Message.ToString(); 
 }
 finally
 {

}
 return result;
 }

Retrieving an Exchange Mailbox

Now that we can send emails with K2 related data we now need to be able to retrieve those emails. So we can then view them in a SmartForm.

The first thing we need

public static List<EmailBox> GetMailBox(string MailBoxType,int PageSize)
 {
 ItemView view = new ItemView(PageSize);
 List<EmailBox> list = new List<EmailBox>();

Guid SN_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition SN_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(SN_PropertySetId, "SN", MapiPropertyType.String);

Guid Folio_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition Folio_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(Folio_PropertySetId, "Folio", MapiPropertyType.String);

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);

Guid BusinessKey_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition BusinessKey_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(BusinessKey_PropertySetId, "BusinessKey", MapiPropertyType.String);

Guid ProcessTypeId_PropertySetId = Guid.Parse("d6520129-3c59-4191-b9d7-4f5160329e4f");
 ExtendedPropertyDefinition ProcessTypeId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessTypeId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);

Guid MessageId_PropertySetId = Guid.Parse("6e997d14-d9b3-4516-8d14-0a10b0aa74aa");
 ExtendedPropertyDefinition MessageId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(MessageId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);

ExchangeService service = ConnectToExchange();
 view.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject, SN_ExtendedPropertyDefinition, Folio_ExtendedPropertyDefinition, ProcessInstanceId_ExtendedPropertyDefinition, BusinessKey_ExtendedPropertyDefinition, ProcessTypeId_ExtendedPropertyDefinition, MessageId_ExtendedPropertyDefinition);

FindItemsResults<Item> findResults = service.FindItems((MailBoxType == "Sent" ? WellKnownFolderName.SentItems : WellKnownFolderName.Inbox), view);
 foreach(Item email in findResults.Items)
 {
 Item mail = Item.Bind(service, email.Id);
 list.Add(new EmailBox
 {
 MailBoxType = MailBoxType,
 Subject = mail.Subject,
 Body = mail.Body,
 Importance = mail.Importance.ToString(),
 Id = mail.Id.ToString(),
 Categories = mail.Categories.ToString(),
 DateTimeCreated = mail.DateTimeCreated,
 DateTimeReceived = mail.DateTimeReceived,
 DateTimeSent = mail.DateTimeSent,
 Cc = mail.DisplayCc,
 To = mail.DisplayTo,
 SN = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[0].Value.ToString():string.Empty),
 Folio = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[1].Value.ToString(): string.Empty),
 ProcessInstanceId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[2].Value.ToString(): string.Empty),
 BusinessKey = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[3].Value.ToString(): string.Empty),
 ProcessTypeId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[4].Value.ToString(): string.Empty),
 MessageId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[5].Value.ToString(): string.Empty)

});

}
 return list;

}

d

de

Retrieve an Email

Now that we can retrieve a list of emails from a mailbox we now need to be able to retrieve a single email.

We can do this.ww

public static EmailBox GetEmail(string Id)
 {
 EmailBox email = new EmailBox();
 ExchangeService service = ConnectToExchange();

try
 {
 Item mail = Item.Bind(service, (ItemId)Id);
 {
 email.Subject = mail.Subject;
 email.Body = mail.Body;
 email.Importance = mail.Importance.ToString();
 email.Id = mail.Id.ToString();
 email.Categories = mail.Categories.ToString() ;
 email.DateTimeCreated = mail.DateTimeCreated;
 email.DateTimeReceived = mail.DateTimeReceived;
 email.DateTimeSent = mail.DateTimeSent;
 email.Cc = mail.DisplayCc;
 email.To = mail.DisplayTo;
 email.SN = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[0].Value.ToString(): string.Empty);
 email.Folio = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[1].Value.ToString(): string.Empty);
 email.ProcessInstanceId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[2].Value.ToString(): string.Empty);
 email.BusinessKey = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[3].Value.ToString(): string.Empty);
 email.ProcessTypeId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[4].Value.ToString(): string.Empty);
 email.MessageId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[5].Value.ToString(): string.Empty);
}
}
 catch(Exception ex)
 { }
 finally
 {

}
 return email;
 }

 

Now that we have these methods to send an email, retrieve a mailbox and to retrieve an email. We can now register the library as an endpoint assembly. 

We could extend this to be able to add attachments and we could also look at the calendar meeting requests and doing the same with those and extend their properties 

We can then build a SmartObject around it and then we can use it within are workflows and Smartforms. To make it even easier for people to use the new email SmartObject, we could wrap a SmartWizard around the methods.

The full solution can be downloaded from here 

 

Slack and Simple K2 Integration

One of the big messaging apps for team collaboration is Slack and  is perfect platform to demo how to integrate K2 into it. This will be the first article on how we go about doing this.  Slack is free to use and so is great to demo with and it has an ever growing list of third party integration plugins to play with.

So before we start with the demo,what would we expect a K2 slack plugin to behave? I believe it behave in the following way.

  1. Send notifications of tasks that we have to actiontask
  2. Be notified of when task has been completed and what the action was or the workflow has moved to a certain stage or when there is a workflow escalation.
  3. To be able to action a task from inside Slackhomepage_example_hiretron
  4. To be ask Slack what are my outstanding tasks or what is the status of a certain workflow

So lets starts with points 1 and 2 and deal with simple notifications.

 

Building a Simple messaging app for Slack

Lets start with a simple example, where we can K2 notification messages to Slack, whether its a public message , message to a particular group or a message to an individual person.

First of all we need to sign up for Slack and create a team, which you can down from here . Now we have a slack team, we just need to here  to get access to API for Slack.

api_slack

We are starting simple, so click on “Incoming webhooks” and then click on the link ‘ incoming webhook integration

introwebhooks

Building the web hook url

  1. Choose the channel you want to send the messages to, don’t we will be able to override this later on.

introwebhooks12. Click on the green button ‘Add incoming Webhook integration’

3. You can how see your web hook url, copy that.

introwebhooks24. Further down you can also customize the actual message. I have opted for a K2 look.

introwebhooks35. Click on ‘Save’, we have now created are web hook for incoming messaging.

Slack endpoint assembly

Now we have the web hook, we can how write some code, so K2 can use it. We are going to use a endpoint assembly for this. So we are going to create a class that will take the endpoint and allow us to pass in a message, a optional username and optional group.

private static void PostMessage(Payload payload)
 {
 Encoding _encoding = new UTF8Encoding();
 Uri endpoint = new Uri("web hook here");

string payloadJson = JsonConvert.SerializeObject(payload);
 
 using (System.Net.WebClient client = new System.Net.WebClient())
 {
 System.Collections.Specialized.NameValueCollection data = 
new System.Collections.Specialized.NameValueCollection();
 data["payload"] = payloadJson;

var response = client.UploadValues(endpoint, "POST", data);

//The response text is usually "ok"
 string responseText = _encoding.GetString(response);
 }
 }

Simple code for posting a payload of information to the web hook url

public static void PostMessage(string text, string username = null, string channel = null)
 {
 Payload payload = new Payload()
 {
 Channel = channel,
 Username = username,
 Text = text
 };

PostMessage(payload);
 }

The actual public static method, we will be creating a SmartObject from and then using inside a workflow.

We can then build the solution and take the dll  and now tell K2 about it using the ‘Endpoint Assembly broker’. If you don’t know how to do that view my previous post on creating an Endpoint Assembly.

K2Service

Now just build a SmartObject that uses the service instance you just created.

Slack SMO

We can test it in the SmartObject tester

smotester

When it executes we get this response in Slack

smoslack

No that the SmartObject has been created, we can now use this method inside a workflow

Workflow

I am just going to use a simple workflow for this, that has one task and two actions.

test-workflow

We just going to use a SmartObject event to call the Slack notification SmartObject to send a message to the destination user when a task is generated and then a message to the originator when the task is approved or rejected.

SlackSMOEVENT1

SlackSMOEVENT2.PNG

SlackSMOEVENT4

We do something similar for the approve and reject activities, except we put in the originator name and the message is that the task has been approved or rejected depending on the activity.

When we run the workflow the destination user gets this message in Slack

task

With a link to the task, when they action the task the originator will get this message

task1

Next time we will expand on this by making the notifications more advanced and by allowing the user to ask questions about K2.

The source code for this example can be downloaded from here

Testing a workflow inside a Smartform

So I have posted a few articles on testing in K2, we have looked at Unit Testing,  Testing Smartforms and then just recently more examples of testing a workflow.  After writing that article I realised that there was something missing. What about building a workflow testing tool inside of a Smartform. As it all well and good that we can write unit tests for testing a workflow, but that relies on the workflow developer being able to write code and being that the whole idea of K2 is to build low code , no code solutions. Then there should be away of doing it without the developer having to write code.

There are a number of tools out in the market and they do absolutely fantastic job of testing workflows without having to write code. But I wanted to see if it could be done inside a Smartform.

So my challenge is to build a Smartform App that would test a workflow, with out the tester having to write any code.

The app had to do the following

  1. Start any workflow and check that it has started
  2. Check that tasks have been created and the correct number
  3. Action a task
  4. Checking the task has been completed
  5. Check that the workflow has finished
  6. Check the workflow has gone down the direct path.

The app should all be in Smartforms and should be easy to use.

So lets see how I got on.  So i am going to start with the finished tool and then i will take us through it.

Testing Dashboard

The dashboard shows all the current tests that have been performed and shows the overall percentage of pass and failures.

dashboard

From here you can also click on ‘Create Test’ button to create a new test. Selecting an existing test and clicking on ‘Run Test’ will  run an existing test. Finally double clicking on a test will open up the details about the test.

Test Details

So the test details shows the following information

test details

  1. Workflow being tested
  2. If has already been tested
  3. When it was tested
  4. Overall Pass or Fail
  5. What percentage passed and failed
  6. What was tested and whether each test passed or failed.
  7. It also shows any other tests relating to the workflow being tested.

From the test details you also have the ability to retest the test as well.

Creating a new test

From the dashboard click on ‘Create Test’ button.

test builder.PNG

  1. In the new test form, give the test a name
  2. Select a workflow that you are going to test. Once selected you will then be able to see all the activities of that workflow beneath.test-builder1
  3. Select the activities that the workflow will go throughtest-builder2
  4. The last section is where we build up the actual the tests and what results we expect from those tests.test-builder3

Building a simple test script

The workflow we have selected is a very simple one. It has the following activities

  1. Setup
  2. Task
  3. Approve
  4. Reject
  5. End

test-workflow

Task is where there is a client event which has one slot and goes to the originator.

Now that we have got the basic test details and what route we expect the test to go. We can now build are test script.

  1. Select ‘Start Process’ from the test type drop down list
  2. You will notice that the other text boxes in the row, will be pre populated with information and some will be disabled depending on what information the particular test type requires.
  3. Now we choose comparison sign we are going to use it can be either
    1.  =
    2. >
    3. >=
    4. <
    5. <=
  4. We are going to choose >
  5. In the expected result / comparison box lets enter 0, this is because if workflow starts successfully we expect a value greater than 0.
  6. So ‘Start Process’, needs to know the ‘Folio’ to give the workflow, you can either enter in something. Or if you leave it blank it will take the name we have given earlier to the test.
  7. The next parameter we need to give the test type is the process name. You can either copy the full workflow name and paste into this box or leave it and it will automatically get the workflow name.
  8. The next box is called milliseconds, you can either leave it at 0. Or you can enter in a value. This is useful as it may be your workflow is taking a while to perform an event. So you can enter in a delay to the tests to compensate for this.
  9.  Click on ‘Add’ again , we now what to check the status of the workflow, to make sure that once we started it. It is now active.
  10. So select ‘Get Workflow Status’ from the drop down list
  11. Choose ‘=’ from Sign
  12. Enter ‘Active’ in expected result
  13. We don’t need to enter in anything else
  14. Click on ‘Add’
  15. We now want to check to see if has generated some tasks
  16. Select ‘Get Task Count’
  17. Select ‘=’ in Sign
  18. Expected Result should be 1, as we know it’s only one slot and going to the originator.
  19. ProcessInstanceId, leave that how it is, as the testing framework will replace that with the actual process instance id as run time of the test
  20. Process name either enter in the full name or leave it and it will get automatically populated
  21. Next we can action the task
  22. Select ‘=’ for the sign
  23. In the expected result box enter ‘True’
  24. Where it’s says ‘Action’ we need to enter in the action we expect the test to use. So lets enter ‘Approve’
  25. Where it says ‘Serial Number’ we leave that and like the process instance id, it will be populated by the testing framework at run time.
  26. For Destination user, enter in the destination user that the task is supposed to be for.
  27. When this test type runs it will try and action the task based on the information we have given it (Task Action and Destination User) if the test is successful the actual result will be true.
  28. The last test we are going to do is to check that workflow has gone down the correct path and the correct activities were used. The test will compare the path we expect it to be. which was defined in the ‘Workflow Route’ section to the actual path taken.
  29. Select ‘Compare Activities’ from ‘Test Type’
  30. Select ‘=’ from Sign
  31. Enter in ‘True’ in expected result
  32. Leave ProcessInstanceId and Activities how it is
  33. In the Millisecond box enter ‘4000’ this will make the ‘Compare Activities’ test to wait 4000 milliseconds before starting the test. This will give the workflow time to perform it’s other activities before completing. If this test fails then increase the time. It will then pass.
  34. The test script should now look like thistest-builder4
  35. Now click on ‘Save’
  36. You will now see a read only version of the test
  37. With two buttons ‘Edit Test’ and ‘Run Test’

Running a Test

To run a test, either select a test from the test list and click on ‘Run Test’ or from the test details click on the button ‘Run Test’

Once the test has been completed, you will be displayed the results for each individual test and also the overall test.

resuts

Each individual test will show whether it has passed or failed. If it has passed the status will be true and in green and if had failed it will be false and in red.

For each test that we create we also get the overall test result.

A failed test would be when any of the individual tests has failed

fail

It will show a pie chart showing the % of pass and fail with big black x cross above it.

pass

For an overall Pass we get a tick and of course a 100%

These test results will remain until, you click on ‘Run Test’ again and this will overwrite the previous test results with the new ones.

Continue reading “Testing a workflow inside a Smartform”

Workflow Testing

In a previous article i went through how to unit test K2 workflow, now i have some more examples of testing a workflow from start to finish.

These unit tests just use the out of the box K2 API and Visual Studio’s testing framework. It uses an XML file to store test results and data such as task SN and process instance id.

The code can be downloaded from here 

Starting the workflow

To start a workflow we can use the following API

// <summary>
 /// Starts a workflow
 /// </summary>
 /// <param name="Folio"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public int StartProcess(string Folio, string ProcessName)
 {
 var con = ConfigurationManager.AppSettings;
 int ProcessInstanceId = 0;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.CreateProcessInstance(ProcessName);
 
 K2Proc.Folio = Folio;
 K2Conn.ImpersonateUser(ServiceAccount);
 K2Conn.StartProcessInstance(K2Proc);
 ProcessInstanceId = K2Proc.ID;
 }
 catch (Exception EX)
 {
 ProcessInstanceId = 0;
 }
finally
 {
 K2Conn.Close();
 }
 return ProcessInstanceId;
 }

This method returns the process instance id. We can use this in a unit test and check to see if the value returned from the above method is greater than 0

Test: Unit Test for starting the workflow and check to see if it has started (Common)

/// <summary>
 /// Tests the starting of a workflow, should return a number greater than 0
 /// </summary>
 /// <param name="folio"></param>
 /// <param name="ProceesName"></param>
 /// <returns></returns>
 [TestMethod]
 public void StartWorkflow_Success()
 {
int actual = workflow.StartProcess(this.folio, this.ProcessName);
 this.proceessInstance = actual;
 Results.SaveResult(TestType.ProcessInstance,this.proceessInstance.ToString()); 
 Assert.AreNotEqual(0, actual);
 ProcInstId = this.proceessInstance;
 
 }

Check the workflow is running

Now that the workflow has started, we can run a method to check it’s current status

/// <summary>
 /// Gets the current status of the workflow
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public string GetWorkflowStatus(int processinstanceId)
 {
 string Result = string.Empty;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 switch (K2Proc.Status1)
 {
 case SourceCode.Workflow.Client.ProcessInstance.Status.Active:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString();
 break;
 }case SourceCode.Workflow.Client.ProcessInstance.Status.Completed:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Completed.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Deleted:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Deleted.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Error:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Error.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.New:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.New.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Running:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Running.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Stopped:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Stopped.ToString();
 break;
 }
 }  
 }
 catch (Exception ex)
 { Result = ex.Message; }
 finally
 {
 K2Conn.Close();
 }return Result;
 }

This method returns the current status, we can use this to see if the current workflow is running or if has completed

Test: Check workflow has started (Common)

/// <summary>
 /// Test to check that the workflow has started, by checking it's status
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 [TestMethod]
 public void StartWorkflow_Running_Success()
 {
int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 string actual = workflow.GetWorkflowStatus(this.proceessInstance);
 Results.SaveResult(TestType.StartStatus, actual);
 StringAssert.Equals(SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString(), actual);
 }

We could also take this same unit test and change the SourceCode.Workflow.Client.ProcessInstance.Status.Active to ‘Error’ or ‘Completed’

so we can create tests to see if the workflow is completed or it has gone into error state

 

Checking to see if  a task has been created

This method check to see if a task has been created for the particular process and instance.

public Boolean IsTaskFound(int ProcessInstanceId, string ProcessName)
 {
 Boolean Result = false;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
 foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 Result = true;
 }
 }
 }
 catch (Exception ex)
 {
Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Work List – task found (Common)

Returns true if task has been found

/// <summary>
 /// Tests to see if tasks have been generated for this instance
 /// </summary>
 [TestMethod]
 public void WorkList_TasksFound_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 Boolean actual = false;
 actual = workflow.IsTaskFound(this.proceessInstance, this.ProcessName);
 Assert.AreEqual(true, actual);
 }

 

 

Checking the correct number of tasks has been created

This method gets the number of tasks that has been generated. So we can check if the correct number of tasks.

public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Correct number of tasks created (Common)

Returns the number of tasks, which we can then compare against the amount expected

/// <summary>
 /// Checks that the correct number of tasks is generated
 /// </summary>
 [TestMethod]
 public void WorkList_CorrectNumberOfTasksCreated_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(this.TaskCount, actual);
 }

 

Get task details

This methods takes the task, and gets the details about the task

/// <summary>
 /// Gets details about the task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public List<tasklist> GetTask(int ProcessInstanceId,string ProcessName)
 {
List<tasklist> list = new List<tasklist>();
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 var x = worklistItem.ActivityName;
list.Add(new tasklist
 {
 Status = worklistItem.Status.ToString(),
 Destination = worklistItem.Destination,
 EventName = worklistItem.EventName,
 ActInstDestID = worklistItem.ActInstDestID.ToString(),
 ActivityName = worklistItem.ActivityName,
 SerialNumber = (ProcessInstanceId + "_" + worklistItem.ActInstDestID)
 });

}
 }
 }
 catch (Exception ex)
 {
// Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return list;
 }

 

Test:  Check that the correct task has been created (Common)

Returns the name of the activity which compares against the expected activity

/// <summary>
 /// Gets the activity name of the task
 /// </summary>
 [TestMethod]
 public void WorkList_RetrieveTaskList_Success()
 {
 string Actual = string.Empty;
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
var task = workflow.GetTask(this.proceessInstance, this.ProcessName);
 
 this.SerialNumber = task[0].SerialNumber;
 Results.SaveResult(TestType.SerialNumber, this.SerialNumber);
 this.DestinationUser = task[0].Destination;
 Results.SaveResult(TestType.Destination, this.DestinationUser);
 Actual = task[0].ActivityName;
 Results.SaveResult(TestType.TaskActivity, Actual);
 Assert.AreEqual(this.TaskActivity, Actual);
}

 

Action a task

Actions the task and moves the workflow along

/// <summary>
 /// Actions a task
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean ActionTask(string action, string serialnumber, string destinationuser)
 {
 Boolean result = false;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = true;
 }
 catch (Exception ex)
 {
 result = false;
 }
 finally {
 K2Conn.Close();
 }
 return result;
 }

Test : Action’s the task

Returns true if the action of the task has been successful which we compare against the expected.

/// <summary>
 /// Actions a task
 /// </summary>
 [TestMethod]
 public void Task_GetActions_Sucess()
 {
 Boolean Actual = false;
 this.SerialNumber = Results.GetResult(TestType.SerialNumber);
 this.DestinationUser = Results.GetResult(TestType.Destination);
K2WorkflowMap.TaskDetails task = workflow.OpenTask(this.SerialNumber, this.DestinationUser)[0];
 Actual = workflow.ActionTask(this.TaskAction, this.SerialNumber, this.DestinationUser);
 Assert.AreEqual(true, Actual);
}

This test only accounts for one slot, it can be modified to loop through the task list for the particular instance.

 

Task Count

This method, counts the number of tasks for the current instance

 /// <summary>
 /// Gets the number of active tasks for instance of a workflow
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Check Task Complete (Common)

Test checks to see if the number of the tasks for instance is 0

/// <summary>
 /// checks to see if task is complete by checking that task has gone
 /// </summary>
 [TestMethod]
 public void Task_CheckTaskComplete_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(0, actual);
 }

 

 

Once the main lot of testing is complete we can check that that the correct number of activities have been executed and we can check that the correct activities have ran.

Also using the status method we can check if its completed.

 

Activities correct number

This method gets all the activities that ran during the process instance.

/// <summary>
 /// Gets the list of activities from process instance
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<Activities> GetActivities(string processinstanceId)
{
List<Activities> list = new List<Activities>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Activity_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ProcessInstanceID"].Value = processinstanceId;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Activities
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 ActivityName = item.Properties["ActivityName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Activities
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 ActivityName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally {
serverName.Connection.Close();
 }
return list;
 }

 

Task: Compare Number Of Activities (Common)

This test compares the number of activities that actually ran for the instance against the number expected

/// <summary>
 /// Checks the correct number of activities ran
 /// </summary>
 [TestMethod]
 public void Activities_CompareNumberOfActivities_Success()
 {
 
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
int NumberOfActivitiesRan = workflow.GetActivities(this.proceessInstance.ToString()).Count;
 String[] activities = this.Activities.Split(';');
 int count = 0;
 foreach (var activity in activities)
 {
 count++;
}
Assert.AreEqual(count, NumberOfActivitiesRan);
 }

 

Activities than ran

This method loops through the activities that have ran against the an array of activities

public Boolean CompareActivities(int ProcessInstanceId, string Activities)
 {
Boolean result = false;
List<Activities> instAct = new List<Activities>();
 try
 {
 instAct = GetActivities(ProcessInstanceId.ToString());
String[] activities = Activities.Split(';');
 int count = 0;
 int match = 0;
 foreach (var activity in activities)
 {
 var instanceactivity = instAct.Where(p => p.ActivityName == activity);
foreach (var act in instanceactivity)
 {
 match++;
 }
count++;
 }
if (count == match)
 { result = true; }
 }
 catch (Exception ex)
 { }
 finally { }
return result;
}

Test: Correct Activities Ran (Common)

This test returns true if the activities ran is the same to the activities in the string array.

 /// <summary>
 /// Checks the correct activities were executed in correct order
 /// </summary>
 /// 
 [TestMethod]
 public void Activities_CorrectActivitiesRan_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
Boolean actual = workflow.CompareActivities(this.proceessInstance, this.Activities);
 Assert.IsTrue(actual);
 }

 

Code Appendix

Below is the complete code

Interface Iworkflowframework.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace K2WorkflowMap
{
 interface Iworkflowframeworkcs
 {
int StartProcess(string Folio, string ProcessName);
 Boolean IsTaskFound(int ProcessInstanceId, string ProcessName);
 int GetTaskCount(int ProcessInstanceId, string ProcessName);
List<tasklist> GetTask(int ProcessInstanceId, string ProcessName);
 List<TaskDetails> OpenTask(string serialnumber, string destination);
List<ActionList> WhatCanIdo(SourceCode.Workflow.Client.Actions actions);
Boolean ActionTask(string action, string serialnumber, string destinationuser);
Boolean IsTaskComplete(int ProcInst, int Count, string processname);
Boolean IsTaskComplete(string action, string serialnumber, string destinationuser);
string GetWorkflowStatus(int processinstanceId);
List<dataField> GetProcessDataFields(int processinstanceId);
List<Activities> GetActivities(string processinstanceId);
List<Events> GetEvents(string activityid);
List<ActivityDesign> GetWorkflowActivities(int ProcId);
List<Workflow> GetProcesses();
}
}

 

Class Model.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace K2WorkflowMap
{
public enum TestToRun
 {
 Start = 1,
 CheckStatus = 2,
 TaskExists = 3,
 TaskCount = 4,
 CheckTask = 5,
 OpenTaskDetails = 6,
 TaskActions = 7,
 CompleteTask = 8,
 GetActivities = 9
}
 public class tasklist
 {
 public string ActInstDestID { get; set; }
 public string ActivityName { get; set; }
 public string EventName { get; set; }
 public string Destination { get; set; }
 public string Status { get; set; }
 public string SerialNumber { get; set; }
}
public class ActionList
 {
 public string ActionValue { get; set; }
 public string ActionText { get; set; }
 }
public class TaskDetails
 {
public string Status { get; set; }
 public string SerialNumber { get; set; }
 public SourceCode.Workflow.Client.Actions Actions { get; set; }
 public string Data { get; set; }
 public string DataFields { get; set; }
 }
 public class dataField
 {
 public string Name { get; set; }
 public string Value { get; set; }
 public string valueType { get; set; }
 }
public class Activities
 {
 public int ActivityInstanceId { get; set; }
 public int ProcessInstanceId { get; set; }
 public string ActivityName { get; set; }
 public DateTime StartDate { get; set; }
public DateTime FinishDate { get; set; }
 public String Status { get; set; }
 }
public class Events
 {
public int ActivityInstanceId { get; set; }
 public int ProcessInstanceId { get; set; }
 public string EventName { get; set; }
 public string Destination { get; set; }
 public DateTime StartDate { get; set; }
public DateTime FinishDate { get; set; }
 public String Status { get; set; }
 }
public class ActivityDesign
 {
 public int ID { get; set; }
 public string Name { get; set; }
 public string Description { get; set; }
 }
public class Workflow
 {
 public int ID { get; set; }
 public string Name { get; set; }
}
}

Class Results.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.IO;
namespace K2WorkflowUnitTest
{
 public enum TestType
 {
ProcessName,
 Folio,
 ProcessInstance,
 StartStatus,
 TaskCount,
 SerialNumber,
 Destination,
 TaskActivity,
 Actions,
 Activities,
 
 }
 public enum TestSubTypes
 {
Action,
 Activity,
 Event,
 }
public class Results
 {
 private string assemblyFolder;
 private string xmlFileName;
 public Results()
 {
 this.assemblyFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
 this.xmlFileName = Path.Combine(assemblyFolder, "DataStoreResults.xml");
 }
 public void SetUpXML(string ProcessName, string Folio)
 {
 //Gets the settings file
 if (!File.Exists(xmlFileName))
 {
 XmlTextWriter writer = new XmlTextWriter(xmlFileName, System.Text.Encoding.UTF8);
 writer.WriteStartDocument(true);
 writer.Formatting = Formatting.Indented;
 writer.WriteStartElement("Settings");
 writer.WriteStartElement("ProcessName");
 writer.WriteString(ProcessName);
 writer.WriteEndElement();
 writer.WriteStartElement("Folio");
 writer.WriteString(Folio);
 writer.WriteEndElement();
 writer.WriteStartElement("ProcessInstanceId");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("StartStatus");
 writer.WriteString("Stop");
 writer.WriteEndElement();
 writer.WriteStartElement("TaskCount");
 writer.WriteAttributeString("Actual","0");
 writer.WriteEndAttribute();
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("SerialNumber");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Destination");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("TaskActivity");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Actions");
 writer.WriteStartElement("Action");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteStartElement("ActionResult");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Activities");
 
 writer.WriteStartElement("Activity");
 
 writer.WriteString("0");
 writer.WriteStartElement("Event");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndDocument();
 writer.Close();
 }
 else
 {
XmlDocument xmlDoc = new XmlDocument();
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[0].InnerXml = ProcessName;
 xmlDoc.ChildNodes[1].ChildNodes[1].InnerXml = Folio;
 xmlDoc.Save(xmlFileName);
 }

}


 /// <summary>
 /// Saves the result into XML Document
 /// </summary>
 /// <param name="testType"></param>
 /// <param name="Result"></param>
 public void SaveResult(TestType testType, string Result)
 {
 int childNodeIndex = 0;
 childNodeIndex = (int)testType;
 XmlDocument xmlDoc = new XmlDocument();
 try
 {
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[childNodeIndex].InnerXml = Result;
 xmlDoc.Save(xmlFileName);
 }
 catch (Exception ex)
 {
}
 finally
 {
}
}
 /// <summary>
 /// Saves the result of sub node in XML Document
 /// </summary>
 /// <param name="testType"></param>
 /// <param name="subTestType"></param>
 /// <param name="Result"></param>
public void SaveResult(TestType testType, TestSubTypes subTestType ,string Result)
 {
 int childNode = 0;
 childNode = (int)testType;
int childNodeIndex = 0;
 childNodeIndex = (int)subTestType;
XmlDocument xmlDoc = new XmlDocument();
 try
 {
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[childNode].ChildNodes[childNodeIndex].InnerXml = Result;
 xmlDoc.ChildNodes[1].ChildNodes[childNode].ChildNodes[childNodeIndex].Attributes["Name"].Value = Result;
 xmlDoc.Save(xmlFileName);
 }
 catch (Exception ex)
 {
}
 finally
 {
}
}
 
 /// <summary>
 /// Gets a saved result
 /// </summary>
 /// <param name="testType"></param>
 /// <returns></returns>
 public string GetResult(TestType testType)
 {
int childNodeIndex = 0;
 childNodeIndex = (int)testType;
string results = string.Empty;
 XmlDocument xmlDoc = new XmlDocument();
 xmlDoc.Load(xmlFileName);
 results = xmlDoc.ChildNodes[1].ChildNodes[childNodeIndex].InnerXml;
 return results;
}

}
}

Class Workflowframework.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
namespace K2WorkflowMap
{

public class WorkflowInstanceFramework : Iworkflowframeworkcs
 {
 public string servername = "cft-v-k2int02";
 public string ServiceAccount = @"CHANNEL4\SVCK2MINTMULEUSER";


/// <summary>
 /// Starts a workflow
 /// </summary>
 /// <param name="Folio"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public int StartProcess(string Folio, string ProcessName)
 {
 var con = ConfigurationManager.AppSettings;
 int ProcessInstanceId = 0;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.CreateProcessInstance(ProcessName);
 
 K2Proc.Folio = Folio;
 K2Conn.ImpersonateUser(ServiceAccount);
 K2Conn.StartProcessInstance(K2Proc);
 ProcessInstanceId = K2Proc.ID;
 }
 catch (Exception EX)
 {
 ProcessInstanceId = 0;
 }
finally
 {
 K2Conn.Close();
 }
 return ProcessInstanceId;
 }
/// <summary>
 /// Checks to see if there is an task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public Boolean IsTaskFound(int ProcessInstanceId, string ProcessName)
 {
 Boolean Result = false;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
 foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 Result = true;
 }
 }
 }
 catch (Exception ex)
 {
Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

/// <summary>
 /// Gets the number of active tasks for instance of a workflow
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }
/// <summary>
 /// Gets details about the task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public List<tasklist> GetTask(int ProcessInstanceId,string ProcessName)
 {
List<tasklist> list = new List<tasklist>();
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 var x = worklistItem.ActivityName;
list.Add(new tasklist
 {
 Status = worklistItem.Status.ToString(),
 Destination = worklistItem.Destination,
 EventName = worklistItem.EventName,
 ActInstDestID = worklistItem.ActInstDestID.ToString(),
 ActivityName = worklistItem.ActivityName,
 SerialNumber = (ProcessInstanceId + "_" + worklistItem.ActInstDestID)
 });

}
 }
 }
 catch (Exception ex)
 {
// Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return list;
 }
 /// <summary>
 /// Task Details
 /// </summary>
 /// <param name="serialnumber"></param>
 /// <param name="destination"></param>
 /// <returns></returns>
 public List<TaskDetails> OpenTask(string serialnumber, string destination)
 {
List<TaskDetails> list = new List<TaskDetails>();
SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destination);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
try
 {
list.Add(new TaskDetails
 {
 Status = K2WListItem.Status.ToString(),
 SerialNumber = K2WListItem.SerialNumber,
 Actions = K2WListItem.Actions,
 Data = K2WListItem.Data,
 DataFields = K2WListItem.ProcessInstance.DataFields.ToString()
 });
 K2WListItem.Release();
 }
 catch (Exception ex)
 {
 list.Add(new TaskDetails
 {
 Status = "No Task"
 });
}
 finally {
 K2Conn.Close();
 }
 return list;
 }
/// <summary>
 /// List of Actions
 /// </summary>
 /// <param name="actions"></param>
 /// <returns></returns>
 public List<ActionList> WhatCanIdo(SourceCode.Workflow.Client.Actions actions)
 {
 List<ActionList> list = new List<ActionList>();
 if (actions != null)
 {
 foreach (SourceCode.Workflow.Client.Action action in actions)
 {
 list.Add(new ActionList
 {
 ActionText = action.Name,
 ActionValue = action.Name
 });
 }
 }
 else {
list.Add(new ActionList
 {
 ActionText = "No Action",
 ActionValue = "No Action"
 });
 }
return list;
 }

 /// <summary>
 /// Actions a task
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean ActionTask(string action, string serialnumber, string destinationuser)
 {
 Boolean result = false;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = true;
 }
 catch (Exception ex)
 {
 result = false;
 }
 finally {
 K2Conn.Close();
 }
 return result;
 }
/// <summary>
 /// Checks to see if the task count has decreased
 /// </summary>
 /// <param name="ProcInst"></param>
 /// <param name="Count"></param>
 /// <returns></returns>
 public Boolean IsTaskComplete(int ProcInst, int Count,string processname)
 {
 Boolean result = false;
 int LastCount = Count;
 int NewCount = GetTaskCount(ProcInst,processname);
 if (LastCount == Count)
 {
 result = false;
 }
 else
 {
 result = true;
 }
return result;
 }
/// <summary>
 /// Checks to see if the actual task that was actioned has been successful
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean IsTaskComplete(string action, string serialnumber, string destinationuser)
 {
 Boolean result = true;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = false;
 }
 catch (Exception ex)
 {
 result = true;
 }
 finally
 {
 K2Conn.Close();
 }
 return result;
 }
/// <summary>
 /// Gets the current status of the workflow
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public string GetWorkflowStatus(int processinstanceId)
 {
 string Result = string.Empty;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 switch (K2Proc.Status1)
 {
 case SourceCode.Workflow.Client.ProcessInstance.Status.Active:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString();
 break;
 }
case SourceCode.Workflow.Client.ProcessInstance.Status.Completed:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Completed.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Deleted:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Deleted.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Error:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Error.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.New:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.New.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Running:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Running.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Stopped:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Stopped.ToString();
 break;
 }
 }


 }
 catch (Exception ex)
 { Result = ex.Message; }
 finally
 {
 K2Conn.Close();
 }
return Result;
 }
/// <summary>
 /// Gets the process data fields
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<dataField> GetProcessDataFields(int processinstanceId)
 {
 List<dataField> list = new List<dataField>();
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 foreach (SourceCode.Workflow.Client.DataField datafield in K2Proc.DataFields)
 {
 list.Add(new dataField {
Name = datafield.Name,
 Value = datafield.Value.ToString(),
 valueType = datafield.ValueType.ToString()
});
 }
 }
 catch (Exception ex)
 {
 list.Add(new dataField
 {
Name = "Error",
 Value = ex.Message.ToString()
});
}
 finally {
 K2Conn.Close();
 }
return list;
 }
 public Boolean CompareActivities(int ProcessInstanceId, string Activities)
 {
Boolean result = false;
List<Activities> instAct = new List<Activities>();
 try
 {
 instAct = GetActivities(ProcessInstanceId.ToString());

String[] activities = Activities.Split(';');
 int count = 0;
 int match = 0;
 foreach (var activity in activities)
 {
 var instanceactivity = instAct.Where(p => p.ActivityName == activity);
foreach (var act in instanceactivity)
 {
 match++;
 }
count++;
 }
if (count == match)
 { result = true; }
 }
 catch (Exception ex)
 { }
 finally { }
return result;
}


/// <summary>
 /// Gets the list of activities from process instance
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<Activities> GetActivities(string processinstanceId)
{
List<Activities> list = new List<Activities>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Activity_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ProcessInstanceID"].Value = processinstanceId;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Activities
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 ActivityName = item.Properties["ActivityName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Activities
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 ActivityName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally {
serverName.Connection.Close();
 }
return list;
 }
/// <summary>
 /// Gets a list of Events for an activity
 /// </summary>
 /// <param name="activityid"></param>
 /// <returns></returns>
 public List<Events> GetEvents(string activityid)
 {
 List<Events> list = new List<Events>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Event_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ActivityInstanceID"].Value = activityid;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Events
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 EventName = item.Properties["EventName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate,
 Destination = item.Properties["Destination"].Value
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Events
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 EventName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally
 {
serverName.Connection.Close();
 }
return list;
 }
public void IsErrors(int processinstanceId, string workflowname)
 {



}


/// <summary>
 /// Gets all the activities that are in a workflow
 /// </summary>
 /// <param name="ProcId"></param>
 /// <returns></returns>
 public List<ActivityDesign> GetWorkflowActivities(int ProcId)
 {
 List<ActivityDesign> list = new List<ActivityDesign>();
 SourceCode.Workflow.Management.WorkflowManagementServer workflowServer = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 workflowServer.Open();
 try
 {
 
 foreach (SourceCode.Workflow.Management.Activity activity in workflowServer.GetProcActivities( ProcId))
 {
 list.Add(new ActivityDesign
 {
ID = activity.ID,
 Name = activity.Name,
 Description = activity.Description
});

 }
 }
 catch (Exception ex)
 {
 list.Add(new ActivityDesign
 {
ID = 0,
 Name = "Error",
 Description = ex.Message
 });
 }
 finally
 {
 workflowServer.Connection.Close();
 }
 return list;
}
/// <summary>
 /// Gets a list of all the workflows
 /// </summary>
 /// <returns></returns>
 public List<Workflow> GetProcesses()
 {
 List<Workflow> list = new List<Workflow>();
SourceCode.Workflow.Management.WorkflowManagementServer workflowServer = new SourceCode.Workflow.Management.WorkflowManagementServer(servername,5555);
 workflowServer.Open();
 try
 {
 SourceCode.Workflow.Management.Criteria.ProcessCriteriaFilter filter = new SourceCode.Workflow.Management.Criteria.ProcessCriteriaFilter();
 SourceCode.Workflow.Management.Processes processes = workflowServer.GetProcesses(filter);
 foreach (SourceCode.Workflow.Management.Process process in processes)
 {
 if (process.DefaultVersion == true)
 {
 list.Add(
 new Workflow
 {
 ID = process.ProcID,
 Name = process.FullName
}
);
 }
 }
 }
 catch (Exception ex)
 {
 list.Add(
 new Workflow
 {
 ID = 0,
 Name = ex.Message
}
);
 }
 finally
 {
 workflowServer.Connection.Close();
 }
 return list;
 }
 }
}

 

XML DataStoreResult.xml

Used to hold test data

<?xml version="1.0" encoding="utf-8" ?>
<Settings>
<ProcessName>k2-server-int02</ProcessName>
<Folio></Folio>
<ProcessInstanceId>5555</ProcessInstanceId>
<StartStatus>End</StartStatus>
<TaskCount Actual="1">0</TaskCount>
 <SerialNumber>00_00</SerialNumber>
 <Destination>Bob</Destination>
 <Actions>
 <Action>Approve</Action>
 </Actions>
 <ActionResult>Approve</ActionResult>
 <Activities>
 <Activity>
 <Event name="Status Check">Pass</Event>
 </Activity>
 </Activities>
</Settings>

Unit Test Unittest.cs

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
namespace K2WorkflowUnitTest
{
 [TestClass]
 public class UnitTest1
 {
private string folio;
 private string ProcessName;
 private int proceessInstance;
 private string SerialNumber;
 private string TaskAction;
 private int TaskCount = 0;
 private string Activities;
 private string DestinationUser;
 private string TaskActivity;
 private int milliseconds;
 private SourceCode.Workflow.Client.Actions Actions;
 private K2WorkflowMap.WorkflowInstanceFramework workflow;
 private Results Results;
public int ProcInstId { get; set; }
[TestInitialize]
 public void TestInitialize()
 {
 this.folio = "I am a folio 3";
 this.ProcessName = @"K2Project2\TestProcess";
 this.TaskActivity = "Task";
 this.TaskAction = "Approve";
 this.Activities = "Setup;Task;Approve;End";
 this.TaskCount = 1;
 this.milliseconds = 3000;
 workflow = new K2WorkflowMap.WorkflowInstanceFramework();
 Results = new Results();
 Results.SetUpXML(this.ProcessName, this.folio);
 }
 /// <summary>
 /// Tests the starting of a workflow, should return a number greater than 0
 /// </summary>
 /// <param name="folio"></param>
 /// <param name="ProceesName"></param>
 /// <returns></returns>
 [TestMethod]
 public void StartWorkflow_Success()
 {
int actual = workflow.StartProcess(this.folio, this.ProcessName);
 this.proceessInstance = actual;
 Results.SaveResult(TestType.ProcessInstance, this.proceessInstance.ToString());
 Assert.AreNotEqual(0, actual);
 ProcInstId = this.proceessInstance;
}
/// <summary>
 /// Test to check that the workflow has started, by checking it's status
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 [TestMethod]
 public void StartWorkflow_Running_Success()
 {
int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 string actual = workflow.GetWorkflowStatus(this.proceessInstance);
 Results.SaveResult(TestType.StartStatus, actual);
 StringAssert.Equals(SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString(), actual);
 }

/// <summary>
 /// Tests to see if tasks have been generated for this instance
 /// </summary>
 [TestMethod]
 public void WorkList_TasksFound_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 Boolean actual = false;
 actual = workflow.IsTaskFound(this.proceessInstance, this.ProcessName);
 Assert.AreEqual(true, actual);
 }
 /// <summary>
 /// Checks that the correct number of tasks is generated
 /// </summary>
 [TestMethod]
 public void WorkList_CorrectNumberOfTasksCreated_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(this.TaskCount, actual);
 }
 /// <summary>
 /// Gets the activity name of the task
 /// </summary>
 [TestMethod]
 public void WorkList_RetrieveTaskList_Success()
 {
 string Actual = string.Empty;
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
var task = workflow.GetTask(this.proceessInstance, this.ProcessName);
 
 this.SerialNumber = task[0].SerialNumber;
 Results.SaveResult(TestType.SerialNumber, this.SerialNumber);
 this.DestinationUser = task[0].Destination;
 Results.SaveResult(TestType.Destination, this.DestinationUser);
 Actual = task[0].ActivityName;
 Results.SaveResult(TestType.TaskActivity, Actual);
 Assert.AreEqual(this.TaskActivity, Actual);
}
 /// <summary>
 /// Actions a task
 /// </summary>
 [TestMethod]
 public void Task_GetActions_Sucess()
 {
 Boolean Actual = false;
 this.SerialNumber = Results.GetResult(TestType.SerialNumber);
 this.DestinationUser = Results.GetResult(TestType.Destination);
K2WorkflowMap.TaskDetails task = workflow.OpenTask(this.SerialNumber, this.DestinationUser)[0];
 Actual = workflow.ActionTask(this.TaskAction, this.SerialNumber, this.DestinationUser);
 Assert.AreEqual(true, Actual);
}
/// <summary>
 /// checks to see if task is complete by checking that task has gone
 /// </summary>
 [TestMethod]
 public void Task_CheckTaskComplete_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(0, actual);
 }
 /// <summary>
 /// Checks the correct number of activities ran
 /// </summary>
 [TestMethod]
 public void Activities_CompareNumberOfActivities_Success()
 {
 
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
int NumberOfActivitiesRan = workflow.GetActivities(this.proceessInstance.ToString()).Count;
 String[] activities = this.Activities.Split(';');
 int count = 0;
 foreach (var activity in activities)
 {
 count++;
}
Assert.AreEqual(count, NumberOfActivitiesRan);
 }
/// <summary>
 /// Checks the correct activities were executed in correct order
 /// </summary>
 /// 
 [TestMethod]
 public void Activities_CorrectActivitiesRan_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
Boolean actual = workflow.CompareActivities(this.proceessInstance, this.Activities);
 Assert.IsTrue(actual);
 }


}
}

Unit Testing in K2

Introduction

I have been researching testing K2 workflows for a client over the last couple of days and I came across this white paper in my library and I thought I would share it with you all.

Development teams have been using automated tools, such as Visual Studio Test System (VSTS), as part of their Software Development Lifecycle to automate the unit testing of the application software artifacts. This document covers how to use VSTS to automate the unit testing of K2 processes. The same principles can be applied using other automated unit testing frameworks such as NUnit.

K2 processes are exposed directly through a client API as well as through a service layer. The service layer includes both WCF, REST, and legacy SOAP (WS) services. This document discusses how to use the client API, SourceCode.Workflow.Client, in VSTS unit tests to start and action K2 processes to test that the processes behave as expected.

This document shows how to set up a Visual Studio solution to test K2 processes. The solution includes a Framework class library project that provides some classes to help with unit testing to reduce the amount of code in each unit test and to help provide a common structure across tests. A set of best practices for unit testing K2 processes is then discussed. The best practices are ultimately followed by a set of How-To’s that describe how to perform different types of tests.

 

Sample K2 Workflow

The following depicts a sample K2 process that we will unit test as part of the examples in this document. It contains a standard approval, a rework step that sends a task back to the originator, a placeholder step that has a line rule, and then a couple of placeholders for the approve and deny branches.

sample

 

Setup the VSTS Solution

This section walks the reader through how to set up a solution in Visual Studio to use VSTS test projects to test K2 processes. This section describes how to set up a solution from scratch. It is perfectly acceptable to add the test project to an existing solution if that is preferred for a particular project.

Create Visual Studio Solution and Test Projects

This section outlines the steps necessary to get started. It includes high level instructions for creating the Visual Studio solution and projects that we will use in further exercises. It assumes the reader has familiarity with Visual Studio and does not include detailed instructions.

Create Visual Studio Solution

Create an empty solution found in the Visual Studio Other Project Types category. You can use an existing solution if you want to add the process test projects to a suite of other unit tests.

Create Visual Studio Test Framework Project

Create a standard class library project in your solution. This library will house common entities and a ProcessHelper class that includes much of the common code used in unit tests to start and move processes along their paths. Add the following references:

  • System.Configuration
  • SourceCode.Workflow.Client
  • SourceCode.Workflow.Management
  • SourceCode.HostClientAPI
  • SourceCode.Data.SmartObjectsClient
  • c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll

 

Create Visual Studio Test Project

Add a test project to the solution. The test project template can be found under the Test Projects\Test Documents node in the Installed Templates tree in the Add New Project dialog. Add the following references:

  • Add a reference to the test framework class library project
  • Add a reference to SourceCode.Workflow.Client

Your solution explorer should look similar to the following:

solutionexplorer

 

Configure the Framework Project

The framework project contains the following:

  • ProcessHelper – A helper class that contains utility functions for common tasks performed in unit tests. This pulls this code out of the unit tests to make them quicker to develop, easier to read, and focused on the scenario it is testing.
  • IProcessTest – This is an interface that test classes must implement to use the ProcessHelper
  • TestScenarioContext – A simple data class to keep the process context for a test scenario

Change Target Framework

Change the target framework to .NET Framework 4 by right clicking on the project, select Properties, and in the dropdown for Target framework select .NET Framework 4.

Add TestScenarioContext Class

Right click the project name and select Add -> New Folder. Make the folder name Entities. Right click the Entities folder and select Add Class. Name the class TestScenarioContext. This class is used to track the process instance data of a test run. Paste this class definition (changing namespaces to match your namespace).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SourceCode.Workflow.Client;
 
namespace SourceCode.Examples.UnitTests.Framework.Entities
{
 public class TestScenarioContext
 {
 public ProcessInstance ProcessInstance { get; set; }
 public Guid TestInstanceId { get; set; }
 
 public long ProcessId
 {
 get
 {
 long id = -1;
 if (this.ProcessInstance != null)
 {
 id = this.ProcessInstance.ID;
 }
 return id;
 }
 } 
 }
}

Add IProcessTest Interface

Add a folder to the framework project and name the folder Interfaces. Add the following interface to the folder. This interface is used by test projects to pass into the ProcessHelper classes as a reference back into the test class to be able work with the process instance and other process data.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using SourceCode.Examples.UnitTests.Framework.Entities;

using Microsoft.VisualStudio.TestTools.UnitTesting;




namespace SourceCode.Examples.UnitTests.Framework.Interfaces

{

       public interface IProcessTest

       {

              TestScenarioContext TestScenarioContext { get; set; }

              TestContext TestContext { get; set; }

              string ProcessName { get; }

       }

}

 

Add ProcessHelper Class

The ProcessHelper class provides helper methods for starting and working with processes and activities. Right click on the Framework project and click Add -> Class… Name the class ProcessHelper and copy the following code to the class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SourceCode.Examples.UnitTests.Framework.Interfaces;
using SourceCode.Workflow.Client;
using System.Configuration;
using SourceCode.Data.SmartObjectsClient;
using System.Data;
 
namespace SourceCode.Examples.UnitTests.Framework
{
 public static class ProcessHelper
 {
 public static string ConnectionString
 {
 get { return ConfigurationManager.AppSettings["K2WorkflowServerConnectionString"]; }
 }
 
 public static string ConnectionStringManagementServer
 {
 get { return ConfigurationManager.AppSettings["K2ManagementServerConnectionString"]; }
 }
 
 public static string ConnectionStringImpersonation
 {
 get { return ConfigurationManager.AppSettings["K2WorkflowServerConnectionStringImpersonate"]; }
 }
 
 public static void StartProcess(IProcessTest testClass)
 {
 StartProcess(testClass, null, true);
 }
 
 public static string SOConnectionString
 {
 get { return ConfigurationManager.AppSettings["K2SOConnectionString"]; }
 }
 
 public static string WorkflowServer
 {
 get { return ConfigurationManager.AppSettings["K2WorkflowServer"]; }
 }
 
 public static int WorkflowServerPort
 {
 get
 {
 int port = 0;
 bool parsed = Int32.TryParse(ConfigurationManager.AppSettings["K2WorkflowServerPort"], out port);
 if (!parsed)
 {
 port = 5555;
 }
 return port;
 }
 }
 
 public static void StartProcess(IProcessTest testClass, Dictionary<string, string> dataFields)
 {
 StartProcess(testClass, dataFields, true);
 }
 
 public static void StartProcess(IProcessTest testClass, Dictionary<string, string> dataFields, bool sleep)
 {
 ConnectionSetup connSetup = new ConnectionSetup();
 connSetup.ConnectionString = ConnectionString;
 using (Connection conn = new Connection())
 {
 conn.Open(connSetup);
 ProcessInstance instanceToStart = conn.CreateProcessInstance(testClass.ProcessName);
 
 if (dataFields != null)
 {
 foreach (KeyValuePair<string, string> field in dataFields)
 {
 instanceToStart.DataFields[field.Key].Value = field.Value;
 }
 }
 
 instanceToStart.Folio = GetFolio(testClass);
 
 conn.StartProcessInstance(instanceToStart, true);
 
 testClass.TestScenarioContext.ProcessInstance = instanceToStart;
 } 
 
 if (sleep)
 {
 System.Threading.Thread.Sleep(2000);
 }
 }
 
 public static void StopProcess(IProcessTest testClass)
 {
 try
 {
 SourceCode.Workflow.Management.WorkflowManagementServer server = new SourceCode.Workflow.Management.WorkflowManagementServer();
 server.Open(ConnectionStringManagementServer);
 server.StopProcessInstances(Convert.ToInt32(testClass.TestScenarioContext.ProcessId));
 }
 catch { } // ignore, don't want to fail the test if the process can't be stopped
 }
 
 /// <summary>
 /// Gets worklist items for the current running user
 /// </summary>
 /// <param name="testClass"></param>
 /// <returns></returns>
 public static Worklist GetWorklist(IProcessTest testClass)
 {
 return GetWorklist(testClass, null);
 }
 
 /// <summary>
 /// Gets worklist items for the username that is passed in. Pass in the username as domain\user
 /// </summary>
 /// <param name="testClass"></param>
 /// <param name="user"></param>
 /// <returns></returns>
 public static Worklist GetWorklist(IProcessTest testClass, string user)
 {
 ConnectionSetup connSetup = new ConnectionSetup();
 
 if (String.IsNullOrEmpty(user))
 {
 connSetup.ConnectionString = ConnectionString;
 }
 else
 {
 connSetup.ConnectionString = ConnectionStringImpersonation;
 }
 
 using (Connection conn = new Connection())
 {
 conn.Open(connSetup);
 if (!String.IsNullOrEmpty(user))
 {
 conn.ImpersonateUser(user); 
 }
 
 WorklistCriteria criteria = new WorklistCriteria();
 criteria.AddFilterField(WCField.ProcessFullName, WCCompare.Equal, testClass.ProcessName);
 
 // filter by the folio as well so we only get worklist items back for this process instance
 criteria.AddFilterField(WCField.ProcessFolio, WCCompare.Equal, GetFolio(testClass));
 
 Worklist list = conn.OpenWorklist(criteria);
 return list;
 }
 }
 
 public static void ActionWorklistItem(IProcessTest testClass, WorklistItem worklistItem, string action)
 {
 ConnectionSetup connSetup = new ConnectionSetup();
 connSetup.ConnectionString = ConnectionStringImpersonation;
 
 using (Connection conn = new Connection())
 {
 conn.Open(connSetup);
 
 // need to impersonate the allocated user in order to action the item
 conn.ImpersonateUser(worklistItem.AllocatedUser);
 
 // need to open the item so there is a current socket open
 WorklistItem openItem = conn.OpenWorklistItem(worklistItem.SerialNumber);
 
 if (openItem != null)
 {
 foreach (SourceCode.Workflow.Client.Action itemAction in openItem.Actions)
 {
 if (itemAction.Name == action)
 {
 itemAction.Execute(true);
 break;
 }
 }
 }
 }
 
 // sleep the thread to give time for the logs to be written
 System.Threading.Thread.Sleep(2000);
 }
 
 public static bool EnsureActivityExecuted(IProcessTest testClass, string activityName)
 {
 bool executed = false;
 string query = "select * from Activity_Instance.List where ProcessInstanceID = " + testClass.TestScenarioContext.ProcessId.ToString();
 using (SOConnection soConn = new SOConnection(SOConnectionString))
 using (SOCommand command = new SOCommand(query, soConn))
 using (SODataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
 {
 soConn.Open();
 while (reader.Read())
 {
 string executedActivityName = reader["ActivityName"].ToString(); 
 if (executedActivityName.Equals(activityName))
 {
 executed = true;
 break;
 }
 }
 }
 
 return executed;
 }
 
 public static string GetFolio(IProcessTest testClass)
 {
 string folio = "Unit Test - " + testClass.TestContext.TestName + " - Test Instance ID: " + testClass.TestScenarioContext.TestInstanceId.ToString();
 return folio;
 }
 }
}

 

Configure the Test Project

This section adds the necessary files and configures a build step to copy the application configuration file to the output directory.

Add Application Configuration File

You can specify a configuration file to use for a unit test project by creating a file that conforms to the naming convention of <ProjectName>.dll.config. For instance, if you have a project named MyCompany.UnitTests.Process you would add a configuration file named:

MyCompany.UnitTests.Process.dll.config

To add this file, right click on the test project and select Add -> New Item… Select Application Configuration File as the item type and name it according to the convention specified above. Add the following to the configuration file.

NOTE: change the server names and port numbers to match your environment and change the username/password for the K2WorkflowServerConnectionStringImpersonate to match your environment.

<appSettings>

              <add key="K2WorkflowServer" value="dlx"/>

              <add key="K2WorkflowServerPort" value="5555"/>

              <add key="K2SOConnectionString" value="Integrated=True;IsPrimaryLogin=True;Authenticate=True;EncryptedPassword=False;Host=dlx;Port=5555"/>

              <add key="K2WorkflowServerConnectionString" value="Integrated=True;IsPrimaryLogin=False;Authenticate=True;EncryptedPassword=False;Host=localhost;Port=5252"/>

              <add key="K2ManagementServerConnectionString" value="Integrated=True;IsPrimaryLogin=True;Authenticate=True;EncryptedPassword=False;Host=localhost;Port=5555"/>

              <add key="K2WorkflowServerConnectionStringImpersonate" value="Integrated=True;IsPrimaryLogin=True;Authenticate=True;EncryptedPassword=True;Host=localhost;Port=5252;WindowsDomain=denallix;UserID=K2Service;Password=K2pass!"/>

       </appSettings>

Add Build Script to Copy Configuration File to Output Directory

The configuration file created in the above section needs to be copied to the output directory so that it is in the same directory as the dll when the dll is executed. To automate this:

  • Right click on the test project and select Properties
  • Click the Build Events tab
  • In the Post-build event command line text box add the following:
    copy "$(ProjectDir)*.config" "$(TargetDir)"

Now every time the project is built it will copy the configuration file to the output directory.

Best Practices

Now that we have seen how to set up a solution in Visual Studio and how to set up a VSTS project to test K2 processes, let’s take a look at a number of best practices for testing K2 processes. This section will then be followed up with a series of How-To’s that show how to test each of the best practices in VSTS.

The following best practices are included in this section:

  • Scenario vs. Method Paradigm Shift – describes a slightly different way of designing the unit tests due to the fact that unit tests for processes test the entire process rather than testing a discreet unit of functionality
  • Use data fields to test line rules – discusses how to pass in different values to the process data fields to test line rules
  • Use the worklist to test task assignments – discusses how to use the worklist to test task assignments and to execute an action on a task to move the process to the next step
  • Use the K2 reporting SmartObjects to test activity execution – discusses how to use the K2 reporting SmartObjects to test and ensure that the correct activities executed
  • Use XML files for simulating submission of InfoPath forms – discusses how to use XML files stored in the VSTS project to simulate submission of InfoPath forms
  • Use SmartBox SmartObjects to simulate SmartObjects that connect to external systems – discusses how to use SmartBox SmartObjects to be able to manipulate data returned from the SmartObject to test how that data affects the process rules. This is similar to using mock objects in traditional unit testing to simulate connecting to a database.

Scenario vs. Method Paradigm Shift

In a typical unit test paradigm there is a one-to-one mapping between a class in the code library to a class in the unit test library. The class in the unit test library would then fully test the class in the code library. In addition to this, each method in a unit test class generally performs a discreet test against the code library; i.e. unit tests generally don’t test processes that involve multiple methods or properties in the code library.

As one might expect, it would be difficult to use the same paradigm when unit testing a K2 process. Take the simple fact that a process has to be started before a test can be run for something inside the process. This simple scenario breaks the traditional paradigm in that the test has to have code to both start the process and then to test something inside the process. The situation is exacerbated when you need to test an activity that is 10 steps into the process. The test code first has to start the process and then action the first nine steps before it can test the 10th step.

This challenge creates a slight paradigm shift in how unit tests are written for K2 processes. Most unit tests for K2 processes are scenario based rather than based on a single unit. A scenario generally tests one use case. For instance, you may have a scenario that tests an approval at the 10th step and a different scenario that tests a denial at the 10th step.

Scenarios are generally encapsulated within a class in the unit test project. Therefore, you have one class per scenario. This makes the code easier to read and easier to maintain because code to move the process along the various steps can be encapsulated in the class without worrying about name clashes for private methods and properties across scenarios.

Helper classes are typically used as well to move the process along the various steps. The helper class encapsulates common code activities such as starting a process, getting a worklist item, performing an action on a worklist item, updating a data field, etc. This is useful because many scenarios will be very similar to each other with perhaps only one activity having a different outcome than another scenario. In this solution a helper class is included in the Framework assembly. However, there may be cases where a test project may include an individual helper class for common code activities that are private to the process.

Use Data Fields to Test Line Rules

It is common to have line rules in a K2 process that test data fields and execute a branch in the process if the line rule evaluates to true. Line rules can be tested by passing in values for the data field to test both the execution path and the non-execution path. The data field can be set during the process start up or by updating the process definition. Setting the data field at the process start is the most common approach. However, if the data field needs to change mid-way through the process it is possible to change the value of the data field at any time.

Use the Worklist to Test Task Assignments

The worklist API in the WCF services can be leveraged to retrieve the worklist items for a process and then to interrogate those worklist items to ensure that the appropriate items have been created and that they have been assigned to the correct parties. In addition, the worklist API can be used to execute the worklist items and pass in an Action to test that the process correctly continues on to the next activity.

Use the K2 Reporting SmartObjects to Test Activity Execution

The client API does not expose methods to get a list of activities that have been executed for a process. This data is, however, exposed through the K2 Workflow Reports SmartObjects. The How-To section below describes how to retrieve the data using the SmartObject ADO query capabilities.

Use XML Files for Simulating Submission of InfoPath Forms

InfoPath forms are simply XML documents that have a client application and services that understand how to work with the InfoPath XML schema. Therefore, it is possible to simulate the submission of InfoPath forms in a K2 process by passing the XML to the K2 InfoPath service. The How To section describes how to save different versions of the InfoPath XML data as files in the test project and how to read the XML from the file and pass it to the K2 InfoPath service to test different scenarios.

Use SmartBox SmartObjects to Simulate SmartObjects that Connect to External Systems

SmartObjects provide a layer of abstraction between the data that flows from/to a process and the backend system where the data is physically stored. This abstraction provides an opportunity for unit testing, and for development testing, to swap out the physical layer in cases where it isn’t feasible to run automated unit tests against external systems.

For example, the process may integrate with the organization’s HRIS system. Your organization may only have a QA and Production HRIS environment and may not want unit tests run against the QA environment. In this scenario, you can configure the SmartObjects to use a SmartBox backend to simulate reading and writing from the HRIS system for the unit tests. This allows the process to be tested without making any changes to the process itself.

Give Process Rights to the Build Process

A user needs the appropriate rights to start a process. The user that the automated test run under will need these same process rights in order to start the processes. The rights only have to be given to the process once after the process is first deployed to the development server. This can be done manually or a step can be added to the build script to add the process rights.

How-To’s

This section includes a series of code examples that show how to implement the best practices described above.

Start a Process

All test scenarios will need to start a process. The ProcessHelper class has several overloads for starting a process. To start a process with data fields use the overload that accepts a string dictionary. You can set either one or all of the data fields in a process by setting the values in the string dictionary.

This example starts a process without data fields:

ProcessHelper.StartProcess(this);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");

 

This example starts a process and populates a data field named lineRuleField:

Dictionary<string, string> dataFields = new Dictionary<string, string>(1);

                     dataFields.Add("lineRuleField", "Go");




                     ProcessHelper.StartProcess(this, dataFields);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");

Start a Process and Sleep

Depending on the horsepower on the development machine that is running the process it may take varying lengths of time to start the process. If the process takes longer to start than it does to get to your next action in the test script then the test script will fail because the process instance will not be available to work with. Because of this, the StartProcess method in the ProcessHelper automatically sleeps the thread for 2 seconds. You can override this by passing in false for the overload that accepts the sleep parameter.

Get Worklist

The ProcessHelper class in the Framework assembly contains a method GetWorklist that retrieves all the active worklist items for the process. There are two overloads for this method. The overload that does not accept a user will load the worklist for the current user context of the thread. The overload that accepts a user will load the worklist for that user.

Action a Worklist Item

The ProcessHelper class contains an ActionWorklistItem method that will action a worklist item based on the action string passed in. A test script will typically first call the GetWorklist to validate that the worklist contains a task for a user(s). Then it will action the worklist item to move it on to the next step and ensure that the appropriate activity or branch is then executed in the process.

Worklist activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\chad");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     // the user for this task should be chad

                     Assert.AreEqual<string>(@"k2:denallix\chad", activeWorkListItems[0].AllocatedUser.ToLower());




                     ProcessHelper.ActionWorklistItem(this, activeWorkListItems[0], "Approve");




                     bool approveBranchExecuted = ProcessHelper.EnsureActivityExecuted(this, "Approve Branch");

                     Assert.IsTrue(approveBranchExecuted, "Approve Branch did not execute");

 

Ensure an Activity is Executed

When testing line rules or executing an action on a worklist item it is common to subsequently test that the correct activity in the next branch of the workflow executed. The ProcessHelper contains an EnsureActivityExecuted method that accepts the name of the activity. The method returns true if the activity executed or false if it did not.

In some cases the unit test may need to wait for a period of time for the system to write the execution of the activity to the logs. To help with this the ActionWorklistItem in the ProcessHelper automatically sleeps the thread for 2 seconds. If you are not using the ActionWorklistItem method and are seeing that the unit test is failing and indicating that the activity is not executing then you will need to sleep the thread before calling EnsureActivityExecuted in order to give the system some time to write to the logs.

Scenarios for Testing the Sample Process

Basic Process Approve Scenario

The following class tests an approval scenario for the sample process. The class uses the process helper to start a process instance. It then uses the process helper to ensure that only one task is created and then to action the task. Finally, it ensures that the approve branch executed and that the deny branch did not execute.

using System;

using System.Text;

using System.Collections.Generic;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SourceCode.Examples.UnitTests.Framework.Entities;

using SourceCode.Examples.UnitTests.Framework.Interfaces;

using SourceCode.Examples.UnitTests.Framework;

using SourceCode.Workflow.Client;




namespace SourceCode.Examples.UnitTests.Process

{

       [TestClass]

       public class BasicProcessApproveScenario : IProcessTest

       {

              private const string PROCESS_NAME = @"UnitTestingProcesses\BasicProcess";




              private TestScenarioContext _testScenarioContext;

              private TestContext _testContext;




              public TestContext TestContext

              {

                     get { return _testContext; }

                     set { _testContext = value; }

              }




              public TestScenarioContext TestScenarioContext

              {

                     get { return _testScenarioContext; }

                     set { _testScenarioContext = value; }

              }




              public string ProcessName

              {

                     get { return PROCESS_NAME; }

              }




              [TestInitialize]

              public void InitializeTest()

              {

                     _testScenarioContext = new TestScenarioContext();

                     _testScenarioContext.TestInstanceId = Guid.NewGuid();

              }




              [TestCleanup]

              public void CleanupTest()

              {                   

                     _testScenarioContext = null;

              }




              [TestMethod]

              public void TestApproveScenario()

              {

                     ProcessHelper.StartProcess(this);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");




                     Worklist activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\chad");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     // the user for this task should be chad

                     Assert.AreEqual<string>(@"k2:denallix\chad", activeWorkListItems[0].AllocatedUser.ToLower());




                     ProcessHelper.ActionWorklistItem(this, activeWorkListItems[0], "Approve");




                     bool approveBranchExecuted = ProcessHelper.EnsureActivityExecuted(this, "Approve Branch");

                     Assert.IsTrue(approveBranchExecuted, "Approve Branch did not execute");




                     bool denyBranchExecuted = ProcessHelper.EnsureActivityExecuted(this, "Deny Branch");

                     Assert.IsFalse(denyBranchExecuted, "Deny Branch executed when it should not have");

              }

       }

}

 

Basic Process Deny Scenario

The following class tests a deny scenario for the sample process. The class uses the process helper to start a process instance. It then uses the process helper to ensure that only one task is created and then to action the task. Finally, it ensures that the deny branch executed and that the approve branch did not execute.

using System;

using System.Text;

using System.Collections.Generic;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SourceCode.Examples.UnitTests.Framework.Entities;

using SourceCode.Examples.UnitTests.Framework.Interfaces;

using SourceCode.Examples.UnitTests.Framework;

using SourceCode.Workflow.Client;




namespace SourceCode.Examples.UnitTests.Process

{

       [TestClass]

       public class BasicProcessDenyScenario : IProcessTest

       {

              private const string PROCESS_NAME = @"UnitTestingProcesses\BasicProcess";




              private TestScenarioContext _testScenarioContext;

              private TestContext _testContext;




              public TestContext TestContext

              {

                     get { return _testContext; }

                     set { _testContext = value; }

              }




              public TestScenarioContext TestScenarioContext

              {

                     get { return _testScenarioContext; }

                     set { _testScenarioContext = value; }

              }




              public string ProcessName

              {

                     get { return PROCESS_NAME; }

              }




              [TestInitialize]

              public void InitializeTest()

              {

                     _testScenarioContext = new TestScenarioContext();

                     _testScenarioContext.TestInstanceId = Guid.NewGuid();

              }




              [TestCleanup]

              public void CleanupTest()

              {

                     _testScenarioContext = null;

              }




              [TestMethod]

              public void TestDenyScenario()

              {

                     ProcessHelper.StartProcess(this);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");




                     Worklist activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\chad");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     ProcessHelper.ActionWorklistItem(this, activeWorkListItems[0], "Deny");




                     bool denyBranchExecuted = ProcessHelper.EnsureActivityExecuted(this, "Deny Branch");

                     Assert.IsTrue(denyBranchExecuted, "Deny Branch did not execute");




                     bool approveBranchExecuted = ProcessHelper.EnsureActivityExecuted(this, "Approve Branch");

                     Assert.IsFalse(approveBranchExecuted, "Approve Branch executed when it should not have");




              }

       }

}

 

Basic Process Rework Scenario

The following class tests a rework scenario for the sample process. The class uses the process helper to start a process instance. It then uses the process helper to ensure that only one task is created and then to action the manager approval task as a Rework action. It then assures that the administrator has a worklist item for the rework and then executes the Resubmit task for the administrator. Finally, it ensures that Chad had been assigned a worklist item after the Resubmit task was submitted. Note that this scenario does not test the approve or deny branch because it is intended to only test the rework scenario.

using System;

using System.Text;

using System.Collections.Generic;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SourceCode.Examples.UnitTests.Framework.Entities;

using SourceCode.Examples.UnitTests.Framework.Interfaces;

using SourceCode.Examples.UnitTests.Framework;

using SourceCode.Workflow.Client;




namespace SourceCode.Examples.UnitTests.Process

{

       [TestClass]

       public class BasicProcessReworkScenario : IProcessTest

       {

              private const string PROCESS_NAME = @"UnitTestingProcesses\BasicProcess";




              private TestScenarioContext _testScenarioContext;

              private TestContext _testContext;




              public TestContext TestContext

              {

                     get { return _testContext; }

                     set { _testContext = value; }

              }




              public TestScenarioContext TestScenarioContext

              {

                     get { return _testScenarioContext; }

                     set { _testScenarioContext = value; }

              }




              public string ProcessName

              {

                     get { return PROCESS_NAME; }

              }




              [TestInitialize]

              public void InitializeTest()

              {

                     _testScenarioContext = new TestScenarioContext();

                     _testScenarioContext.TestInstanceId = Guid.NewGuid();

              }




              [TestCleanup]

              public void CleanupTest()

              {

                     _testScenarioContext = null;

              }




              [TestMethod]

              public void TestReworkScenario()

              {

                     ProcessHelper.StartProcess(this);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");




                     Worklist activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\chad");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     // the user for this task should be chad

                     Assert.AreEqual<string>(@"k2:denallix\chad", activeWorkListItems[0].AllocatedUser.ToLower());




                     ProcessHelper.ActionWorklistItem(this, activeWorkListItems[0], "Rework");




                     activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\administrator");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     // the user for this task should be chad

                     Assert.AreEqual<string>(@"k2:denallix\administrator", activeWorkListItems[0].AllocatedUser.ToLower());




                     ProcessHelper.ActionWorklistItem(this, activeWorkListItems[0], "Resubmit");




                     activeWorkListItems = ProcessHelper.GetWorklist(this, @"denallix\chad");




                     // in this process there should only be one active worklist item

                     Assert.AreEqual<int>(1, activeWorkListItems.Count);




                     // the user for this task should be chad

                     Assert.AreEqual<string>(@"k2:denallix\chad", activeWorkListItems[0].AllocatedUser.ToLower());




                     ProcessHelper.StopProcess(this);

              }

       }

}

 

 

Basic Process Line Rule Equals Go Scenario

This scenario tests that the Line Rule Activity executes when “Go” is passed in for the lineRuleField data field. This scenario is meant to only test this branch of the process so it does not bother with the manager approval branch.

using System;

using System.Text;

using System.Collections.Generic;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SourceCode.Examples.UnitTests.Framework.Entities;

using SourceCode.Examples.UnitTests.Framework.Interfaces;

using SourceCode.Examples.UnitTests.Framework;

using SourceCode.Workflow.Client;




namespace SourceCode.Examples.UnitTests.Process

{

       [TestClass]

       public class BasicProcessLineRuleEqualGoScenario : IProcessTest

       {

              private const string PROCESS_NAME = @"UnitTestingProcesses\BasicProcess";




              private TestScenarioContext _testScenarioContext;

              private TestContext _testContext;




              public TestContext TestContext

              {

                     get { return _testContext; }

                     set { _testContext = value; }

              }




              public TestScenarioContext TestScenarioContext

              {

                     get { return _testScenarioContext; }

                     set { _testScenarioContext = value; }

              }




              public string ProcessName

              {

                     get { return PROCESS_NAME; }

              }




              [TestInitialize]

              public void InitializeTest()

              {

                     _testScenarioContext = new TestScenarioContext();

                     _testScenarioContext.TestInstanceId = Guid.NewGuid();

              }




              [TestCleanup]

              public void CleanupTest()

              {

                     _testScenarioContext = null;

              }




              [TestMethod]

              public void TestLineRuleBranchEqualsGoScenario()

              {

                     Dictionary<string, string> dataFields = new Dictionary<string, string>(1);

                     dataFields.Add("lineRuleField", "Go");




                     ProcessHelper.StartProcess(this, dataFields);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");




                     bool lineRuleActivityExecuted = ProcessHelper.EnsureActivityExecuted(this, "Line Rule Activity");

                     Assert.IsTrue(lineRuleActivityExecuted, "Line Rule Activity did not execute");




                     ProcessHelper.StopProcess(this);

              }

       }

}

 

 

 

Basic Process Line Rule Equals Stop Scenario

This scenario tests that the Line Rule Activity does NOT execute when “Stop” is passed in for the lineRuleField data field. This scenario is meant to only test this branch of the process so it does not bother with the manager approval branch.

using System;

using System.Text;

using System.Collections.Generic;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SourceCode.Examples.UnitTests.Framework.Entities;

using SourceCode.Examples.UnitTests.Framework.Interfaces;

using SourceCode.Examples.UnitTests.Framework;

using SourceCode.Workflow.Client;




namespace SourceCode.Examples.UnitTests.Process

{

       [TestClass]

       public class BasicProcessLineRuleEqualStopScenario : IProcessTest

       {

              private const string PROCESS_NAME = @"UnitTestingProcesses\BasicProcess";




              private TestScenarioContext _testScenarioContext;

              private TestContext _testContext;




              public TestContext TestContext

              {

                     get { return _testContext; }

                     set { _testContext = value; }

              }




              public TestScenarioContext TestScenarioContext

              {

                     get { return _testScenarioContext; }

                     set { _testScenarioContext = value; }

              }




              public string ProcessName

              {

                     get { return PROCESS_NAME; }

              }




              [TestInitialize]

              public void InitializeTest()

              {

                     _testScenarioContext = new TestScenarioContext();

                     _testScenarioContext.TestInstanceId = Guid.NewGuid();

              }




              [TestCleanup]

              public void CleanupTest()

              {

                     _testScenarioContext = null;

              }




              [TestMethod]

              public void TestLineRuleBranchEqualsStopScenario()

              {

                     Dictionary<string, string> dataFields = new Dictionary<string, string>(1);

                     dataFields.Add("lineRuleField", "Stop");




                     ProcessHelper.StartProcess(this, dataFields);

                     Assert.IsNotNull(this.TestScenarioContext.ProcessInstance, "Unable to start process.");




                     bool lineRuleActivityExecuted = ProcessHelper.EnsureActivityExecuted(this, "Line Rule Activity");

                     Assert.IsFalse(lineRuleActivityExecuted, "Line Rule Activity executed when it should not have");




                     ProcessHelper.StopProcess(this);

              }

       }

}

 

The test project can be downloaded from here

In my next ‘How To’ I will be looking at how to use Specflow, to make testing a workflow more readable to the business.

 

 

Building a K2 Smartform spider part 6

So we have looked at the API and we have made the endpoint assembly and this into a couple of SmartObjects. Now that we have these  we can now  build some View and Smartforms to show the data on the Smartforms.

SmartObject ReCap

So we have two SmartObjects one for the Views and one for the Smartforms.

Spider.smo.Views

Looks at details of the view from it’s basic details to it’s rules

View.smo

Spider.smo.Smartform

Looks at details of the view from it’s basic details to it’s rules

form.smo

 

The views of FormSpider

VIEWSPIDER

Now we can build some views so we can interact with the SmartObjects. Majority of the views will be list views as the methods return back multiple rows of data. The only two that are not going to be list views will be the two forms that allow us to search for a specific Smartform or view.

 

Each of the items views contains the following fields

  1. Artifact name (picker control)
  2. Description (data label)
  3. Version (data label)

view

formheader

Each of the item views has the same rule, which is when the picker control is changed we then transfer the description, version etc.. from the selected row into the view data labels.

pickerchanged

List Views

Parameters

Each of the list views will have the parameter of ‘ViewName’ or ‘FormName’ depending on whether it’s a form view or view view. This allows us to pass in a form name or view name and then load up the parameters for that arifact.

parameters

Properties

We also have view to show all the properties of the artifact

properties

Views in a form and forms that have a certain view in

viewsinform

Controls

The control views shows all the controls on the artifact, whether they are hidden controls such table properties and user controls.

formcontrols

By clicking on a control in the list,  there will be a pop up that show’s all the properties of that control.

CONTROLPROP

Rules

There is also a view to show all the events on the artifact

rules

By clicking on an event,  we are then shown  all the conditions, actions for that event

viewrules

 

SmartObjects

Using the Form Spider assembly, we can also access the details of the SmartObjects, such as its properties, methods and the views the being used by that SmartObject.

SmartObjectsSpider

We could take this even further by getting the details of the service object, but for this version I have just kept it with the basic details

Workflow

Like with the SmartObjects we can also use Form spider to have a look at the workflows on the K2 server as well.

The current version will show the basic details about the workflow such as its description, number of versions etc..

It also shows the process data fields and XML data fields, the activities that make yo the workflow and also  what Smartforms and Views are linked to that particular workflow.

workflowspider

Also by clicking on activity we also get to see the events that make up that activity as well.

workflowevents

Below is a video that goes through the  a demo of Form Spider

Building a K2 Smartform spider part 5

In parts 1 to 4 we looked at some of the different classes and methods that are available in the Smartform API. In part 5 we will be taking these methods and building a complete assembly for us to use.

Smartform spider library

The Smartform spider library is what we will use to create an endpoint assembly so we can create SmartObjects from it and then eventually Smartforms.

Getting Started

1. Open up Visual studio and create a class library project

2.Add the following references to the library

using SourceCode.Forms.Management;
using SourceCode.Forms.Deployment;
using SourceCode.Forms;
using SourceCode.Forms.Authoring;

 

3.Create a class called ‘Common.cs’, this will contain all the common classes to hold the form and view data.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloudFish.FormSpider
{
public class SmartFormViewSml
{
public string name
{
get;
set;
}
public string displayname
{
get;
set;
}

public string description
{
get;
set;
}

public Guid guid
{
get;
set;
}
public int version
{
get;
set;
}
}

public class SmartFormView
{
public string name
{
get;
set;
}
public string displayname
{
get;
set;
}

public string description
{
get;
set;
}

public Guid guid
{
get;
set;
}
public int version
{
get;
set;
}

public string theme
{
get;
set;
}

}

public class SmartFormViewProperties
{
public string name
{

get;
set;
}

public string value
{

get;
set;
}
}
public class SmartFormViewControls
{
public string name
{
get;
set;
}

public string type
{
get;
set;
}

public Guid guid
{
get;
set;
}

}

public class SmartFormViewParameters
{
public string name
{
get;
set;
}

public string type
{
get;
set;
}

public string defaultvalue
{
get;
set;
}
}

public class SmartFormViewEvents
{
public string name
{
get;
set;
}

public string type
{
get;
set;
}

public Guid GUID
{
get;
set;
}
}
public class SmartFromViewHandlers
{

public string Name
{

get;
set;
}

public Guid GUID
{

get;
set;
}

 

public Guid GUID
{

get;
set;
}

}

public class SmartFormViewActions
{
public Guid GUID
{

get;
set;
}

public Guid viewguid
{
get;
set;
}

public Guid formguid
{
get;
set;
}

public string method
{
get;
set;
}

public string executiontype
{
get;
set;
}

public Guid controlguid
{
get;
set;
}

public string actiontype
{
get;
set;
}
}
public class SmartFormViewActionParameters
{

 

public string targettype
{

get;
set;
}
public string targetpath
{

get;
set;
}
public string targetid
{

get;
set;
}

public string sourcevalue
{

get;
set;
}

public string sourcetype
{

get;
set;
}
public string sourcepath
{

get;
set;
}

public string sourceid
{

get;
set;
}
}
public class SmartFormViewActionValidation
{

public string status
{
get;
set;
}
}

public class SmartFormViewActionValidationMessage
{

public string message { get; set; }
}

}

 

4.Create another class called ‘properties.cs’, this will contain a generic method to get the properties of an item.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloudFish.FormSpider
{
class Properties
{

public List<SmartFormViewProperties> ArtefactProperties(SourceCode.Forms.Authoring.PropertyCollection properties)
{
List<SmartFormViewProperties> list = new List<SmartFormViewProperties>();
foreach (SourceCode.Forms.Authoring.Property prop in properties)
{
list.Add(new SmartFormViewProperties
{
name = prop.Name,
value = prop.Value,

});

}
return list;
}
}
}

SmartObject Object

5.Now lets create a class called ‘Smartform.cs’ this class will contain all the public static methods for accessing key information from a Smartform and is the object that will be used as a Service Object by K2.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SourceCode.Forms.Management;
using SourceCode.Forms.Deployment;
using SourceCode.Forms;
using SourceCode.Forms.Authoring;

/// <summary>
/// Explores the content of the SmartForm
/// </summary>
namespace CloudFish.FormSpider
{
public class Smartform
{

/// <summary>
/// Gets the details of the form
/// </summary>
/// <param name=”FormName”></param>
public static SmartFormView LoadForm(string formname)
{

FormsManager frm = new FormsManager(“dlx”, 5555);
FormInfo forminfo = frm.GetForm(formname);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetViewDefinition(formname));

SmartFormView sf = new SmartFormView();
sf.guid = form.Guid;
sf.description = form.Description;

sf.displayname = form.DisplayName;
sf.name = form.Name;
sf.guid = form.Guid;
sf.theme = form.Theme;

return sf;
}
/// <summary>
/// Get a list of all the forms
/// </summary>
/// <returns></returns>
public static List<SmartFormView> GetAllForms()
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);
FormExplorer formexplorer = frm.GetForms();
foreach (SourceCode.Forms.Management.FormInfo forminfo in formexplorer.Forms)
{

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(forminfo.Name));

list.Add(new SmartFormView
{
name = forminfo.Name,
displayname = forminfo.DisplayName,
description = forminfo.Description,
guid = forminfo.Guid,
version = forminfo.Version

});

}
return list;
}
/// <summary>
/// Get a list of all the forms that contain a certain view
/// </summary>
/// <param name=”ViewName”></param>
/// <returns></returns>
public static List<SmartFormView> GetAllFormsbyView(string ViewName)
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);
FormExplorer formexplorer = frm.GetFormsForView(ViewName);
foreach (SourceCode.Forms.Management.FormInfo forminfo in formexplorer.Forms)
{
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetViewDefinition(forminfo.Name));

list.Add(new SmartFormView
{
name = forminfo.Name,
displayname = forminfo.DisplayName,
description = forminfo.Description,
guid = forminfo.Guid,
version = forminfo.Version

});

}
return list;
}
/// <summary>
/// List of form properties
/// </summary>
/// <param name=”FormName”></param>
/// <returns></returns>
public static List<SmartFormViewProperties> GetFormProperties(string FormName)
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
Properties prop = new Properties();
return prop.ArtefactProperties( form.Properties);

}
/// <summary>
/// Gets a list of the form parameters
/// </summary>
/// <param name=”FormName”></param>
/// <returns></returns>
public static List<SmartFormViewParameters> FormParameters(string FormName)
{
List<SmartFormViewParameters> list = new List<SmartFormViewParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));

foreach (SourceCode.Forms.Authoring.FormParameter parameter in form.Parameters)
{
list.Add(new SmartFormViewParameters
{
name = parameter.Name,
type = parameter.DataType.ToString(),
defaultvalue = parameter.DefaultValue
});
}

return list;

}

/// <summary>
/// Form Controls
/// </summary>
/// <param name=”FormName”></param>
/// <returns></returns>
public static List<SmartFormViewControls> FormControls(string FormName)
{
List<SmartFormViewControls> list = new List<SmartFormViewControls>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));

foreach (SourceCode.Forms.Authoring.Control control in form.Controls)
{
list.Add(new SmartFormViewControls
{
name = control.Name,
type = control.Type,
guid = control.Guid,

});

}
return list;
}
/// <summary>
/// Form Events
/// </summary>
/// <param name=”FormName”></param>
/// <returns></returns>
public static List<SmartFormViewEvents> FormEventsEvents(string FormName)
{
List<SmartFormViewEvents> list = new List<SmartFormViewEvents>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));

foreach (SourceCode.Forms.Authoring.Eventing.Event ev in form.Events)
{

if (ev.SourceType == SourceCode.Forms.Authoring.Eventing.EventSourceType.Rule)
{
list.Add(new SmartFormViewEvents
{
name = ev.Name,
type = ev.EventType.ToString(),
GUID = ev.Guid

});

}
}

return list;
}

/// <summary>
/// Event Handlers
/// </summary>
/// <param name=”EventGUID”></param>
/// <returns></returns>
public static List<SmartFromViewHandlers> FormHandlers(String FormName,Guid EventGUID)
{
List<SmartFromViewHandlers> list = new List<SmartFromViewHandlers>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
var ev = form.Events[EventGUID];

SourceCode.Forms.Authoring.Eventing.Event e = form.Events[EventGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Handler handle in e.Handlers)
{
list.Add(new SmartFromViewHandlers
{

Name = handle.HandlerType.ToString(),
GUID = handle.Guid
});

}
return list;

}

/// <summary>
/// Conditions
/// </summary>
/// <param name=”FormName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <returns></returns>
public static List<SmartFormViewConditions> ArtefactConditions(String FormName,Guid EventGUID,Guid HandleGUID)
{
List<SmartFormViewConditions> list = new List<SmartFormViewConditions>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
var ev = form.Events[EventGUID].Handlers[HandleGUID];
SourceCode.Forms.Authoring.Eventing.Handler e = form.Events[EventGUID].Handlers[HandleGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Condition condition in e.Conditions)
{

list.Add(new SmartFormViewConditions
{
GUID = condition.Guid,

});
}

return list;
}
/// <summary>
/// Actions
/// </summary>
/// <param name=”HandleGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActions> ArtefactActionss(String FormName, Guid EventGUID, Guid HandleGUID)
{
List<SmartFormViewActions> list = new List<SmartFormViewActions>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
var ev = form.Events[EventGUID].Handlers[HandleGUID];
SourceCode.Forms.Authoring.Eventing.Handler e = form.Events[EventGUID].Handlers[HandleGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Action action in e.Actions)
{
list.Add(new SmartFormViewActions
{
GUID = action.Guid,

viewguid = action.ViewGuid,
method = action.Method,
formguid = action.FormGuid,
executiontype = action.ExecutionType.ToString(),
controlguid = action.ControlGuid,
actiontype = action.ActionType.ToString()
});
}
return list;
}
/// <summary>
/// Actions Parameters
/// </summary>
/// <param name=”FormName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionParameters> SmartFormViewActionParameters(String FormName, Guid EventGUID, Guid HandleGUID,Guid ActionGUID)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
SourceCode.Forms.Authoring.Eventing.Action e = form.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Mapping map in e.Parameters)
{

list.Add(new SmartFormViewActionParameters
{
targettype = map.TargetType.ToString(),
targetpath = map.TargetPath,
targetid = map.TargetID,
sourcevalue = map.SourceValue,
sourcetype = map.SourceType.ToString(),
sourcepath = map.SourcePath,
sourceid = map.SourceID

});
}

return list;
}
/// <summary>
/// Actions Results
/// </summary>
/// <param name=”FormName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionParameters> SmartFormViewActionResults(String FormName, Guid EventGUID, Guid HandleGUID, Guid ActionGUID)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
SourceCode.Forms.Authoring.Eventing.Action e = form.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Mapping map in e.Results)
{

list.Add(new SmartFormViewActionParameters
{
targettype = map.TargetType.ToString(),
targetpath = map.TargetPath,
targetid = map.TargetID,
sourcevalue = map.SourceValue,
sourcetype = map.SourceType.ToString(),
sourcepath = map.SourcePath,
sourceid = map.SourceID

});
}

return list;
}
/// <summary>
/// Validation Messages
/// </summary>
/// <param name=”FormName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionValidationMessage> SmartFormViewActionValidation(String FormName, Guid EventGUID, Guid HandleGUID, Guid ActionGUID)
{
List<SmartFormViewActionValidationMessage> list = new List<SmartFormViewActionValidationMessage>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(FormName));
SourceCode.Forms.Authoring.Eventing.Action e = form.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.ValidationMessage val in e.Validation.Messages)
{

list.Add(new SmartFormViewActionValidationMessage
{
message = val.Message
});
}

return list;
}

}
}

View Object

6.Create a class called ‘View.cs’, like ‘Smartform.cs’ this class will contain all the methods for accessing information about views  on the K2 environment

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SourceCode.Forms.Management;
using SourceCode.Forms.Deployment;
using SourceCode.Forms;
using SourceCode.Forms.Authoring;

namespace CloudFish.FormSpider
{
public class Views
{

/// <summary>
/// Gets all the views on the environment
/// </summary>
/// <returns></returns>
public static List<SmartFormView> GetAllViews()
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);
ViewExplorer formexplorer = frm.GetViews();
foreach (SourceCode.Forms.Management.ViewInfo viewinfo in formexplorer.Views)
{

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(viewinfo.Name));

list.Add(new SmartFormView
{
name = viewinfo.Name,
displayname = viewinfo.DisplayName,
description = viewinfo.Description,
guid = viewinfo.Guid,
version = viewinfo.Version,
});

}
return list;
}

/// <summary>
/// Gets all the views that are attached to a form
/// </summary>
/// <param name=”formname”></param>
/// <returns></returns>
public static List<SmartFormView> GetAllViews(string formname)
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);
ViewExplorer formexplorer = frm.GetViewsForForm(formname);
foreach (SourceCode.Forms.Management.ViewInfo viewinfo in formexplorer.Views)
{

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(viewinfo.Name));

list.Add(new SmartFormView
{
name = viewinfo.Name,
displayname = viewinfo.DisplayName,
description = viewinfo.Description,
guid = viewinfo.Guid,
version = viewinfo.Version,

});

}
return list;
}
/// <summary>
/// List of view properties
/// </summary>
/// <param name=”ViewName”></param>
/// <returns></returns>
public static List<SmartFormViewProperties> GetViewProperties(string ViewName)
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
Properties prop = new Properties();
return prop.ArtefactProperties(view.Properties);

}
/// <summary>
/// Gets a list of the view parameters
/// </summary>
/// <param name=”ViewName”></param>
/// <returns></returns>
public static List<SmartFormViewParameters> ViewParameters(string ViewName)
{
List<SmartFormViewParameters> list = new List<SmartFormViewParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));

foreach (SourceCode.Forms.Authoring.ViewParameter parameter in view.Parameters)
{
list.Add(new SmartFormViewParameters
{
name = parameter.Name,
type = parameter.DataType.ToString(),
defaultvalue = parameter.DefaultValue
});
}

return list;

}

/// <summary>
/// View Controls
/// </summary>
/// <param name=”ViewName”></param>
/// <returns></returns>
public static List<SmartFormViewControls> ViewControls(string ViewName)
{
List<SmartFormViewControls> list = new List<SmartFormViewControls>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));

foreach (SourceCode.Forms.Authoring.Control control in view.Controls)
{
list.Add(new SmartFormViewControls
{
name = control.Name,
type = control.Type,
guid = control.Guid,

});

}
return list;
}
/// <summary>
/// View Events
/// </summary>
/// <param name=”ViewName”></param>
/// <returns></returns>
public static List<SmartFormViewEvents> ViewEventsEvents(string ViewName)
{
List<SmartFormViewEvents> list = new List<SmartFormViewEvents>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));

foreach (SourceCode.Forms.Authoring.Eventing.Event ev in view.Events)
{
if (ev.SourceType == SourceCode.Forms.Authoring.Eventing.EventSourceType.Rule)
{
list.Add(new SmartFormViewEvents
{
name = ev.Name,
type = ev.EventType.ToString(),
GUID = ev.Guid

});

}
}

return list;
}

/// <summary>
/// Event Handlers
/// </summary>

/// <param name=”ViewName”></param>
/// <param name=”EventGUID”></param>
/// <returns></returns>
public static List<SmartFromViewHandlers> ViewHandlers(String ViewName, Guid EventGUID)
{
List<SmartFromViewHandlers> list = new List<SmartFromViewHandlers>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
var ev = view.Events[EventGUID];

SourceCode.Forms.Authoring.Eventing.Event e = view.Events[EventGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Handler handle in e.Handlers)
{
list.Add(new SmartFromViewHandlers
{

Name = handle.HandlerType.ToString(),
GUID = handle.Guid
});

}
return list;

}

/// <summary>
/// Conditions
/// </summary>
/// <param name=”ViewName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <returns></returns>
public static List<SmartFormViewConditions> ArtefactConditions(String ViewName, Guid EventGUID, Guid HandleGUID)
{
List<SmartFormViewConditions> list = new List<SmartFormViewConditions>();
FormsManager frm = new FormsManager(“dlx”, 5555);
SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
var ev = view.Events[EventGUID].Handlers[HandleGUID];
SourceCode.Forms.Authoring.Eventing.Handler e = view.Events[EventGUID].Handlers[HandleGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Condition condition in e.Conditions)
{

list.Add(new SmartFormViewConditions
{
GUID = condition.Guid

});
}

return list;
}
/// <summary>
/// Actions
/// </summary>
/// <param name=”HandleGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActions> ArtefactActionss(String ViewName, Guid EventGUID, Guid HandleGUID)
{
List<SmartFormViewActions> list = new List<SmartFormViewActions>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
var ev = view.Events[EventGUID].Handlers[HandleGUID];
SourceCode.Forms.Authoring.Eventing.Handler e = view.Events[EventGUID].Handlers[HandleGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Action action in e.Actions)
{
list.Add(new SmartFormViewActions
{
GUID = action.Guid,

viewguid = action.ViewGuid,
method = action.Method,
formguid = action.FormGuid,
executiontype = action.ExecutionType.ToString(),
controlguid = action.ControlGuid,
actiontype = action.ActionType.ToString()
});
}
return list;
}
/// <summary>
/// Actions Parameters
/// </summary>
/// <param name=”ViewName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionParameters> ViewActionParameters(String ViewName, Guid EventGUID, Guid HandleGUID, Guid ActionGUID)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
SourceCode.Forms.Authoring.Eventing.Action e = view.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Mapping map in e.Parameters)
{

list.Add(new SmartFormViewActionParameters
{
targettype = map.TargetType.ToString(),
targetpath = map.TargetPath,
targetid = map.TargetID,
sourcevalue = map.SourceValue,
sourcetype = map.SourceType.ToString(),
sourcepath = map.SourcePath,
sourceid = map.SourceID

});
}

return list;
}
/// <summary>
/// Actions Results
/// </summary>
/// <param name=”ViewName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionParameters> ViewActionResults(String ViewName, Guid EventGUID, Guid HandleGUID, Guid ActionGUID)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
SourceCode.Forms.Authoring.Eventing.Action e = view.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.Eventing.Mapping map in e.Results)
{

list.Add(new SmartFormViewActionParameters
{
targettype = map.TargetType.ToString(),
targetpath = map.TargetPath,
targetid = map.TargetID,
sourcevalue = map.SourceValue,
sourcetype = map.SourceType.ToString(),
sourcepath = map.SourcePath,
sourceid = map.SourceID

});
}

return list;
}
/// <summary>
/// Validation Messages
/// </summary>
/// <param name=”ViewName”></param>
/// <param name=”EventGUID”></param>
/// <param name=”HandleGUID”></param>
/// <param name=”ActionGUID”></param>
/// <returns></returns>
public static List<SmartFormViewActionValidationMessage> ViewActionValidation(String ViewName, Guid EventGUID, Guid HandleGUID, Guid ActionGUID)
{
List<SmartFormViewActionValidationMessage> list = new List<SmartFormViewActionValidationMessage>();
FormsManager frm = new FormsManager(“dlx”, 5555);

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(ViewName));
SourceCode.Forms.Authoring.Eventing.Action e = view.Events[EventGUID].Handlers[HandleGUID].Actions[ActionGUID];

foreach (SourceCode.Forms.Authoring.ValidationMessage val in e.Validation.Messages)
{

list.Add(new SmartFormViewActionValidationMessage
{
message = val.Message

});
}

return list;
}

}
}

7.Now we can build the solution and we are ready to tell K2 about it and make it a service object and then a SmartObject.

You can download the code from this location

 

Creating the Service Object

Creating the service object is really easy, open up the ‘SmartObject Service Tester’

ServiceObjectTester

1.Right Click on the ‘Endpoints Assembly’ and click on ‘Register Service Instance’

AddServiceInstance

2.In the ‘Assembly Full Path’ enter in the full address of where the dll of the assembly is kept.

3.Click on ‘Next’ and enter in ‘Form Spider’ in the ‘Display Name’ and ‘System Name’

4.Click ‘Finish’

K2 will then integrate the Dll and map out all the public static methods.

ServiceObjectTester

Now that we have the service objects we can now create the SmartObjects. For this project i have just created two SmartObjects one for Smartforms and for the Views. Each of these objects will contain all the public static methods we created in the class library earlier.

Below is an example of one

ServiceObjectTester

 

 

 

More information on Endpoint Assemblies can be found here 

 

In the final part of this series we will take the SmartObjects and build the  Smartforms and Views.

 

 

 

 

Building a K2 Smartform spider part 4

Rules

In part 3, we looked at how to get additional information from Smartform such as parameters and controls. In part 4 we will now look at how to be able to get the rules from the forms and views.

This is probably most complex part of the API as it involves a number of different methods to get the  necessary information.

To access the rules  of the artifact we need to use the following code as we did in part 3 this will give us access to the Events object.

Code for a Smartform

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(‘form name’));

Code for a view

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(‘View Name’));

So lets take a look and see what we can from the events object

Events

Majority of rules in Smartforms and views start with an event.  An event is when something happens such as a ‘click of a button’.

public List<SmartFormViewEvents> ArtefactEvents(SourceCode.Forms.Authoring.Eventing.EventCollection events)
{
List<SmartFormViewEvents> list = new List<SmartFormViewEvents>();
foreach (SourceCode.Forms.Authoring.Eventing.Event ev in events)
{

if (ev.SourceType == SourceCode.Forms.Authoring.Eventing.EventSourceType.Rule)
{
list.Add(new SmartFormViewEvents
{
name = ev.Name,
type = ev.EventType.ToString(),
handlers = ev.Handlers,
properties = ev.Properties

});

}
}

return list;
}

The above method gives a list of all the events on the artifact and access to more additional objects

First thing we do is we loop through the event collection and we check to see if the event type is the ‘Rule’ type.

if (ev.SourceType == SourceCode.Forms.Authoring.Eventing.EventSourceType.Rule)

The objects we want to have access to are the following

  1. Name
  2. Type
  3. Handlers
  4. Properties

The important object from this is the Handlers, as this will give us access to the Conditions and Actions of the rule.

Handlers

The handler object is where we can drill down into the rule and access the conditions of the rule and also the actions of the rule.

public List<SmartFromViewHandlers> ArtefactHandlers(SourceCode.Forms.Authoring.Eventing.HandlerCollection handlers)
{
List<SmartFromViewHandlers> list = new List<SmartFromViewHandlers>();
foreach (SourceCode.Forms.Authoring.Eventing.Handler handle in handlers)
{
list.Add(new SmartFromViewHandlers
{
Actions = handle.Actions,
Conditions = handle.Conditions,
Name = handle.HandlerType.ToString()
});

}

return list;

}

The methods starts by passing in the handler object from the previous method, now the method can loop through the all the handlers for that particular event

foreach (SourceCode.Forms.Authoring.Eventing.Handler handle in handlers)

On each loop, we can then access the following objects

  1. Name
  2. Conditions
  3. Actions

Conditions

Conditions check to see if something is equal to true, for example ‘all the required fields are entered’.

public List<SmartFormViewConditions> ArtefactConditions(SourceCode.Forms.Authoring.Eventing.ConditionCollection conditions)
{
List<SmartFormViewConditions> list = new List<SmartFormViewConditions>();
foreach (SourceCode.Forms.Authoring.Eventing.Condition condition in conditions)
{

list.Add(new SmartFormViewConditions
{
Property = condition.Properties
});
}

return list;
}

 

Actions

So we can access the event, the conditions and now lets look how we can access the actions of a rule.

public List<SmartFormViewActions> ArtefactActionss(SourceCode.Forms.Authoring.Eventing.ActionCollection actions)
{
List<SmartFormViewActions> list = new List<SmartFormViewActions>();

foreach (SourceCode.Forms.Authoring.Eventing.Action action in actions)
{
list.Add(new SmartFormViewActions
{
properties = action.Properties,
parameters = action.Parameters,
results = action.Results,
validation = action.Validation,
viewguid = action.ViewGuid,
method = action.Method,
formguid = action.FormGuid,
executiontype = action.ExecutionType.ToString(),
controlguid = action.ControlGuid,
actiontype = action.ActionType.ToString()
});
}
return list;
}

Using the above method, we can get the complete information of the action.  The action opens up some additional objects that we can explore to get more detailed information about the action.

Below are some examples of the more useful ones

Action Parameters

public List<SmartFormViewActionParameters> SmartFormViewActionParameters(SourceCode.Forms.Authoring.Eventing.MappingCollection parameters)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
foreach (SourceCode.Forms.Authoring.Eventing.Mapping map in parameters)
{

list.Add(new SmartFormViewActionParameters
{

validation = map.Validation,
targettype = map.TargetType.ToString(),
targetpath = map.TargetPath,
targetid = map.TargetID,
sourcevalue = map.SourceValue,
sourcetype = map.SourceType.ToString(),
sourcepath = map.SourcePath,
sourceid = map.SourceID

});
}

return list;
}

Action Results

public List<SmartFormViewActionParameters> SmartFormViewActionResults(SourceCode.Forms.Authoring.Eventing.MappingCollection Results)
{
List<SmartFormViewActionParameters> list = new List<SmartFormViewActionParameters>();
foreach (SourceCode.Forms.Authoring.Eventing.Mapping result in Results)
{

list.Add(new SmartFormViewActionParameters
{

validation = result.Validation,
targettype = result.TargetType.ToString(),
targetpath = result.TargetPath,
targetid = result.TargetID,
sourcevalue = result.SourceValue,
sourcetype = result.SourceType.ToString(),
sourcepath = result.SourcePath,
sourceid = result.SourceID

});
}
return list;
}

Action Validation

public List<SmartFormViewActionValidation> SmartFormViewActionValidations(SourceCode.Forms.Authoring.ValidationResult validations)
{

List<SmartFormViewActionValidation> list = new List<SmartFormViewActionValidation>();

SourceCode.Forms.Authoring.ValidationResult validation = validations;

list.Add(new SmartFormViewActionValidation
{
messages = validation.Messages,
status = validation.Status.ToString()
});
return list;
}

Action Messages

public List<SmartFormViewActionValidationMessage> SmartFormViewActionValidationMessages(ValidationMessageCollection messages)
{
List<SmartFormViewActionValidationMessage> list = new List<SmartFormViewActionValidationMessage>();
foreach (ValidationMessage message in messages)
{
list.Add(new SmartFormViewActionValidationMessage
{
message = message.Message
});
}
return list;
}

 

In the next part, we will take the methods we have explored in parts 1 to 4 and put it altogether to build the form spider.

I will make the source code available at the end of this series.

 

Building a K2 Smartform spider part 3

In part 1 we looked at how to get a list of all the Smartforms and views on the K2 server and in part 2 we looked how we could filter those lists by finding all the forms that particular view has been used in etc..

In part 3 we will look at how to go deeper into those artifacts with out actually having to open up the artifact in edit mode to see whats go on.

Getting started

To get started we need some additional objects that are not available in the standard properties that is found in part 1. We need to get  access to the definition of the artifact (basically the XML).

Some of the objects that we will be able to access are

  1.  Properties
  2. Controls
  3. Parameters
  4. Events

To access these additional objects we need to use the following line of code.

Code for a Smartform

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(‘form name’));

Code for a view

SourceCode.Forms.Authoring.View view = new SourceCode.Forms.Authoring.View(frm.GetViewDefinition(‘View Name’));

Parameters

So lets start with an easy one and get the list of parameters that are available on that particular view.

Getting a list of parameters for a form

The below method gets a list of all the parameters for a form

public List<SmartFormViewParameters> ArtifactParameters(SourceCode.Forms.Authoring.FormParameterCollection parameters)
{
List<SmartFormViewParameters> list = new List<SmartFormViewParameters>();
foreach (SourceCode.Forms.Authoring.FormParameter parameter in parameters)
{
list.Add(new SmartFormViewParameters {
name = parameter.Name,
type = parameter.DataType.ToString(),
defaultvalue = parameter.DefaultValue
});
}

return list;

}

The method has the input parameter of type ‘parameter collection’ which we can using the code the code from the ‘Getting Started’ section.

SourceCode.Forms.Authoring.FormParameter parameter in parameters

We then loop through the all the parameters in the collection, where we can get  a list of the  following parameter properties

  1. Name
  2. Type
  3. Default value

Example of executing the method

SourceCode.Forms.Authoring.Form form = new SourceCode.Forms.Authoring.Form(frm.GetFormDefinition(forminfo.Name));

ArtifactParameters(form.parameters);

 

Controls

We had a look at getting the parameters so how about getting a list of all the controls

Like the parameters we pass in the ‘control’ object from the artifact and then we loop through all the controls in the collection.

The properties that we will be  using are the

  1. Name
  2. Type
  3. GUID
  4. Properties (properties of the control)

public List<SmartFormViewControls> ArtefactControls(SourceCode.Forms.Authoring.ControlCollection controls)
{
List<SmartFormViewControls> list = new List<SmartFormViewControls>();
foreach (SourceCode.Forms.Authoring.Control control in controls)
{
list.Add(new SmartFormViewControls {
name = control.Name,
type = control.Type,
guid = control.Guid,
properties = control.Properties

});

}
return list;
}

The control method is universal for both Smartforms and views.

This brings us nicely to the properties object

Properties

The below property method can be used to get the properties of a control such as it’s width, field etc.. But also more properties based from a view or Smartform and even a  rule.

public List<SmartFormViewProperties> ArtefactProperties(SourceCode.Forms.Authoring.PropertyCollection properties)
{
List<SmartFormViewProperties> list = new List<SmartFormViewProperties>();
foreach (SourceCode.Forms.Authoring.Property prop in properties)
{
list.Add(new SmartFormViewProperties
{
name = prop.Name,
value = prop.Value,

});

}
return list;
}

The method accepts the type ‘Property collection’ as an input parameter and then loops through the list of properties.

  1. Name is the name of the property  for example ‘Width’
  2. Value is value of the property for example ‘200px’

 

In the next part we will look at the ‘Events’ as there is a lot that goes into this.

 

I will make the source code available at the end of this series.

 

 

 

Building a K2 Smartform spider part 2

Taking part 1 further

So in part 1 we looked at a couple of basic methods for getting a list of  Smartforms and Views that are currently on the K2 Server. Which can be really handy if you wanted to build a library of all your forms and views.

But can take this further? We can already from the K2 designer expand the form and see what views make up that form, but we can’t though click on a view and see what forms it is being used in.

Finding all forms for a selected view

So in this section I am going to go through how we can retrieve a list of all the forms that a view has been used in. It pretty much uses the same code as the previous examples except this time we will be using  some additional methods that come with the API.

public List<SmartFormView> GetAllFormsForView(string viewName)
{
var list = new List<SmartFormView>();
var frm = new FormsManager(servername, port);
FormExplorer formexplorer = frm.GetFormsForView(viewName);
foreach (SourceCode.Forms.Management.FormInfo forminfo in formexplorer.Forms)
{

list.Add(new SmartFormView
{
name = forminfo.Name,
displayname = forminfo.DisplayName,
description = forminfo.Description,
guid = forminfo.Guid,
version = forminfo.Version,
CheckedOutBy = forminfo.CheckedOutBy,
theme = forminfo.Theme.ToString()
});

}
return list;
}

 

So you can see from the code above , its practically the same as just looping through all the forms. The only difference is this method call

frm.GetFormsForView(viewName)

All we do is call the above method and pass in the name of the view and we can then loop through all the forms  where that view is being used.

Finding all views for a selected form

So as we have done this for the view, it would only be fair if we did this for the form and like the example above it is very similar to the examples in part 1.

public List<SmartFormView> GetAllViews(string formname)
{
List<SmartFormView> list = new List<SmartFormView>();
FormsManager frm = new FormsManager(servername, port);
ViewExplorer formexplorer = frm.GetViewsForForm(formname);
foreach (SourceCode.Forms.Management.ViewInfo viewinfo in formexplorer.Views)
{

list.Add(new SmartFormView
{
name = viewinfo.Name,
displayname = viewinfo.DisplayName,
description = viewinfo.Description,
guid = viewinfo.Guid,
version = viewinfo.Version,

});

}
return list;

}

If we look at the code above the only difference is the method that is being called

frm.GetViewsForForm(formname)

We call this method and pass in the name of the form, or even the GUID and it then returns a list of all the views that are in the form.

Other methods

We can take this even further  and we can get a list of views that is being used by  a SmartObject and even a  list of views/forms that is calling a specific workflow

GetViewsForObject(Smartobject GUID)

GetViewsForProcess(Process Name)

GetFormsForProcess(Process Name)

 

In part 3 we will take these a bit further and drill down into the views and forms to see what makes up those artifacts.

I will make the source code available at the end of this series.