BLACKTOP: Writings From the Road



When you run MS Virtual PC on a drive formatted for FAT32 you might get some unanticipated behavior.  The maximum file size on FAT32 is 4GB - 1 byte.  If your .VHD file needs to grow beyond this limit, VPC will start creating files that have the same name as your .VHD but an extension of .V0n where n starts at 1 and gets incremented every time a new 4GB extension is needed.  VPC automatically starts splitting the HD image across logical files to fit within the FAT32 limitation.  If you want to back up a FAT32-based VHD you must also back up all of the .V0n files.  If you try to start the VPC without the .V0n files, your virtual image will not boot because the guest OS thinks the file system is corrupted.  This also applies to differencing disks.

This might come up because many external USB drives come from the factory formatted as FAT32.  If you want to avoid this altogether, run the VHDs on a NTFS-formatted drive.

 
 


TIP:  When you upload a document to a SharePoint library that has versioning enabled and requires check-in/check-out the document will not appear in the library until after it is checked-in.  On the document upload wizard, check the box to create a K2 Content Field (an XML field that holds metadata about the document).  Then add another SharePoint Document event and take the option to check-in the file using data from the content field.  After that your document will be visible in the library.
 
 


I was using the SharePoint Documents upload event wizard to upload an XML document to a Shared Documents library.  When the process ran, there were no errors, but the document did not appear in the library.  To make a long story short, the default Shared Document library has a document template of Word.  I deleted and re-created the Shared Document library and set the document template to 'None'.  Now the XML document is uploaded successfully when the process runs.  Why not just use a Form Library?  This document library will eventually hold many different kinds of documents, so None is the best choice for a template.

 
 


You have a K2 process that uses InfoPath integration.  It’s been in production for 6 months and working fine.  Along comes Mr. Customer and he’d like to add some new fields to the form and change the process.  Existing processes that have already started should continue using the original form and process but once deployed all new process instances should use the new form.


How do we keep the InfoPath form version in sync with the K2 process?  Although this is a pretty common need, it’s hard to find the K2 documentation that explains how to do it.  Fortunately, it’s really easy.

Content Types

When you’re going through the InfoPath Process Integration wizard, on the deployment location page of click the Add as content type radio button.  This is only available if you are deploying your forms to SharePoint.

When the process is deployed, a new library called K2 InfoPath Form Templates will be created to contain the InfoPath form content types.  As each version of the process is deployed a version of the template will be added to this library.  The process version and form version will be automatically linked.

That's it!

 
 


I've added a few more controls to the enhancement I am working on.  There are now 10 controls in four categories.  All of the label controls allow you to provide a .NET format specifier to control the appearance of the text.

Submit Controls

These controls allow you to interact with a process by starting a new instance or actioning a worklist item.  All of the other controls require one of these two submit controls to be on the same page.

  • K2Start - Starts a new process instance
  • K2Submit - Actions a worklist item  (this is the original control with a few new tricks)

DataField Controls

These controls allow you to get or set the value of a process or activity level data field:

  • K2DataFieldLabel - displays the contents of a data field in a label
  • K2DataFieldTextBox - displays the contents of a data field and allows it to be updated
  • K2DataFieldHyperLink - displays the contents of a data field as a hyperlink
  • K2DataFieldCheckBox - allows you to display the value of a boolean data field as a checkbox

XML Field Controls

These controls are similar to the data field controls, except they work on XML fields.  You can specify an XPath expression to specify a particular element

  • K2XmlFieldLabel - displays the contents of an Xml field in a label
  • K2XmlTextBox - displays the contents of an Xml field and allows it to be updated
  • K2XmlHyperLink - displays the contents of an Xml field as a hyperlink

Workflow Context

This field allows you to display in a label run-time data about the current process or activity instance.  For example, you could display the process folio or activity name.

  • K2WorkflowContextLabel

Using these controls you should be able to quickly put together ASPX pages that interact with process.  Some of the controls even have custom UITypeEditors to help you quickly set properties.

If you would like to volunteer to help test these controls, please send me a private message.

 
 


I'm working on a new version of my blackmarket project.  It started out simply enough.  I was just going to enhance it a little bit to handle the new Out of Office feature released with 807.  It was going to be 100% backwards compatible with the original version.

Oh, but then I got carried away.  I started adding features.  Adding controls.  Adding control designers.  I took suggestions from people who are already using it.  I started asking myself, "What would help developers deliver web pages faster?"  In the end, it's going to have at least 5 or 6 controls instead of just one in the current version.  I've tried to make it easily extensible with a logical inheritance pattern.

In the end though, I decided not to maintain 100% compatibility with the first version.  The K2Submit control will be very similar, but have a different internal structure.  I had to change some of the events it fired slightly so I could take advantage of consistency and inheritance with some of the new controls.

In the end, I hope I have made the control library more useful for you.

 
 


Scenario:
Let’s say you have a client event assigned to a role containing N people.  The succeeding rule is such that everyone must complete their task before the activity completes.  You want to send a reminder email to only those members of the role who have not completed their task after some amount of time but not bother those who have already completed.


Solution:
You can do this easily!  Follow these simple steps:

  • Configure your client event as usual
  • On the activity, go into advanced mode for the destination rule and make the following changes:
    • Plan per destination | All at once
    • Create a slot for each destination
    • Resolve all roles and groups to users
  • Right-click on your event and set up your email escalation
  • On the Email Settings page, click Specify next to Recipient
  • Open the context browser for the To field, go to the Workflow Context Tab, and drill down into Activity Destination Instance and select the user email

That’s it!  Now only those members of the role who have not completed their task by the escalation time will receive an email.  A key thing to remember here is to use the Activity Destination Instance user and do not just click the Destination box for the To field.  If you click the destination option, an email will be sent to every member of the role, even if they have completed their task.


Keep in mind, the plan all at once option uses more server resources than plan just once, so if the number of slots is large then you probably should not use this technique.

 
 


Many times I’ve seen organizations that use InfoPath forms make users fill out the same information about themselves for every request:  name, phone, email, office location, etc.   Call me lazy, but I don’t like to fill out that same information every time.  Besides, what’s more embarrassing than putting a typo in your own name?


I usually create a general SmartObject for this purpose.  General SmartObjects aren’t a K2 concept but a convention I use.  I typically divide SmartObjects into two types:  general and project specific.  General SmartObjects are broadly applicable to many different projects and processes; I name them accordingly.  Specific SmartObjects, while they could be used in other projects, are usually pretty specific to a process.  (There are two kinds of people in this world:  those who divide SmartObjects into two types, and those who don’t).

The general SmartObject I use most is the one that retrieves user and group information from the ActiveDirectory2 service.  In fact, the one I use looks like the one in the “Creating Active Directory SmartObjects” topic starting on page 131 of the K2 book.  Among other things, I use this one to retrieve information about the current user and populate that in the InfoPath form when it opens.

 

I integrate this Users.GetUserDetails SmartObject method into the InfoPath form using either the process wizard in Visual Studio, or by right-clicking on the XSN file and using the integration menu.  In the form’s Open rules I set a condition to make sure it only queries the SmartObject when the initial view is shown.  Then I set the SmartObject input UserName to the InfoPath username() function and query the SmartObject. 

 

Put the fields you want to display on the InfoPath form in read only mode and move the fields from the secondary data source.  Now your users won’t have to enter their information  every time they open the form.  The screen show below shows the information on the InfoPath form along with the corresponding AD entry.  Of course, this information is only as current as the data in ActiveDirectory.  We all keep that current, right?  Right?


 

 
 


I'm not allowed to say anything about what is coming in 090X.  Let me just say this:  WAY COOL!!!
 
 


Sometimes on an ASPX page you want to use a SQLDataSource to insert data into a SmartObject with an auto ID field and then pass that ID when you start a workflow through code.  I’ve done this a number of times, but every time I need to do it I have to think for a few minutes to remember how to do it.  Next time I can just read my own blog.
Take for example this simple SmartObject.  The RequestID field is an autonumber field, meaning the database will automatically increment the value when a row is inserted:

But now I want to pass this value to a process, which is being started in code.  There are a couple of simple things you must do.  First, on you web page, look at the definition of your SQLDataSource that connects to this SmartObject.  Locate your insert parameters, and add Direction=”InputOutput” or Direction=”Output” to the ID parameter:

Next, add an event handler for the Inserted event of the SQLDataSource.  You can find the value of the autonumber like below.  Now you can pass the RequestID to a datafield when you start the process.

 
 


When I’m testing a new process, I like to use the Process Overview report to look at various aspects of my test cases.  But I don’t like it getting cluttered with dozens of completed test cases.  I like to delete my process instances from previous rounds of testing.
How can I delete process instances after they have already completed?  I can’t.  What I do is prevent my process instances from completing.  At the end of the process where it would normally end I add one last server code event and make it asynchronous.  This forces the event to wait forever for some outside action. 

public void Main(Project_0c5d… K2)
{
    K2.Synchronous = false;
}

Then when I want to get rid of the previous round of test cases, I go to the management console and delete them all.  Be sure to check “Delete Log Entries” in the delete dialog.

 
 


I was working with a customer today who had an interesting deployment error:


Error 1 Deploy smartobjects: Task error: SmartObjectServer Exception: Dependancy could not be created: System.Exception: Dependancy could not be created. Parent does not exist in this environment. Check Data property of exception.   at SourceCode.Hosting.Services.DependancyService.VerifyObjects(List`1 parents)   at SourceCode.Hosting.Services.DependancyService.CreateDepenancyBatch(Dictionary`2 depenancyBatch). SmartObject: 'ApplicationIssueObject' Parent ID = '9b17ec8c-177f-4191-98c5-9b908a342e8f'


Many of you may have seen this error when you attempt to deploy a SmartObject based on a dynamic service instance to anther environment.  This has been posted numerous times on this site, but to recap:  SmartObjects bind to the GUID of the underlying service instance, not the name.  When you move between environments and a SmartObject is bound to a dynamic service instance the name might match but the deployment will fail because the GUID is different.  The solution is to change the GUID of the service instance in the target environment to match the GUID of the instance in the environment where the SmartObject was developed.  Or better yet, register the instance with the BrokerManagement utility (in the \ServiceBroker directory) with the same GUID in the first place.


But this customer knew this already and had made sure the GUIDs matched.  After some digging, here is what we found:  the deployed solution contained a SmartObject and a process with a circular dependency.  The workflow was to be started when a row was inserted into the SmartObject from a web page.  The SmartObject had a composite Create method so when a row was inserted, it also invoked the Start method on the workflow SmartObject.  The workflow process had an association to this same SmartObject.  Because of this circular dependency the SmartObject couldn’t be deployed until after the workflow was deployed, and the workflow couldn’t be deployed until after the SmartObject was deployed.


Here’s what we did to get everything deployed:

  1. Make a backup copy of the SmartObject SODX file 
  2. Remove the process.Start from the composite SmartObject Create (and any other methods that refer to the Workflow SmartObject) 
  3. Deploy the SmartObject only, excluding the process 
  4. Deploy the Process only, excluding the SmartObject 
  5. Restore the backup SODX file with the composite methods that depend on the workflow SmartObject 
  6. Deploy the SmartObject again, this time including the composite methods

This only needs to be done the first time you deploy to a new environment.  After it has been deployed once, the dependencies will be met and you can deploy the solution.

 
 


A few weeks ago I was creating a custom SmartObject Service.  During the testing/debugging phase I found I was spending a lot of time stopping the K2 Hostserver, copying the service assembly, and starting the host server again for each change.  Being lazy, I eliminated the manual steps by adding these commands to the post build event in Visual Studio:


net stop "K2 blackpearl Server"
copy $(TargetPath) "C:\Program Files\K2 blackpearl\ServiceBroker\$(TargetFileName)"
net start "K2 blackpearl Server"

This works best if you are doing this in an isolated environment like a VPC.  You’ll drive your co-workers nuts if you restart your shared development server 20 times a day to test your service.  Also, keep in mind if you change the public interface of your service you will also need to refresh the service type definition using the BrokerManagement.exe tool or the SmartObject Service Tester (Amazing SmartObject tool).

I don't remember where I got the idea from, so apologies if it has already been posted somewhere else.

 
 


Scenario:  A customer requested an approach to model a business process where an outstanding client event is no longer needed once a parallel path progresses beyond a business milestone.  In other words, at a certain point in the process the response to a client event is irrelevant and is no longer needed.


Approach:  The ExpireActivity API method can be used in a server code event to expire activities in other parallel paths in the same workflow.  At one point the ExpireActivity call worked the same way it did in K2 2003 (which could only expire the current activity) but it has been updated to expire any specified activity in the current process instance.  
Take for example the simple contrived process below.  The workflow has three parallel paths:  A, B, and C.   Once the client event in path A completes, input from client event B is no longer relevant. 

To cancel, or expire activity B, the server code event called Expire Path B in the Path A activity has a simple line of code to expire Path B:


            K2.ExpireActivity("Path B");

When you expire an activity its line rules are still evaluated.  If you don’t want any of the line rules to be followed, use the K2 context browser to change the definition of the Outcome data field and give it an initial value that does not match any of the line rules.  

If you do want to follow a certain line rule when you expire an activity, give it an initial value that matches the line rule.  In either case, in this example Path C remains unaffected and will work as normal.


What happens if Path B activity has already completed before the ExpireActivity call in Path A?  Nothing!  The ExpireAcitiviy call will have no effect.


 
 


How many out there think there are two different scopes for data and XML fields:  process and activity?   Or are there?  There are actually three different scopes for data fields:  process, activity, and activity destination instance (slot) scope.  Depending on the scope, you get different behavior and access the data fields differently in a server code event.


What’s the scoop on scope?
• Process data/xml fields have scope for the entire life of the process instance
• Activity data/xml fields are in scope only during activity in which they reside and there is only one copy of the field for the whole activity
• Activity destination instance data/xml fields are in scope only during activity in which they reside but there is one copy of the field for each slot in the activity


I think the difference between process scope and activity scope is common knowledge.  But why the difference between activity and activity destination instance?  Suppose you need to capture the distinct action selected by each user in a client event with multiple slots.  In this case, you would use activity destination instance scope.  In this example that was created automatically by a client event wizard, notice the Shared box is not checked.  This is what makes it an activity destination instance field.

To enable complex scenarios in blackpearl, we separate the Actions a user can take from the Outcomes of a client event.  There are plenty of good explanations of Actions & Outcomes posted elsewhere, but for review think of it as the difference between the possible actions any given user can take versus the outcome of a group consensus when there are multiple slots.  Here is the Outcome data field automatically created by a client event wizard.  Notice in this case the Shared box is checked because there will be only one outcome for the whole activity:

How do you access the different data field scopes in a code event?
You can only access activity data fields in a code event or line rule that is in the same activity where the field is declared.  To access the current activity instance fields you must use the Plan Per Destination / Plan All At Once advanced destination rule option.  Below is a simple example that shows how to access the different data fields in a server code event.  The same code will also work if you have only one slot with the Plan Just Once destination option.

            System.Diagnostics.Debug.WriteLine("***Process Data Fields***");
            foreach (DataField df in K2.ProcessInstance.DataFields)
            {
                System.Diagnostics.Debug.WriteLine(df.Name + " = " + df.Value.ToString());
            }

            System.Diagnostics.Debug.WriteLine("***Activity Data Fields***");
            foreach (DataField df in K2.ActivityInstanceDestination.ActivityInstance.DataFields)
            {
                System.Diagnostics.Debug.WriteLine(df.Name + " = " + df.Value.ToString());
            }

            System.Diagnostics.Debug.WriteLine("***Destination Data Fields***");
            foreach (DataField df in K2.ActivityInstanceDestination.DataFields)
            {
                System.Diagnostics.Debug.WriteLine(df.Name + " = " + df.Value.ToString());
            }
 


Final Thoughts…
If you change the definition of a field that you create between activity scope and activity instance, you will have to re-run all wizards where you used it, delete the old field, and add the new field, even if the field name is the same.  That’s because even though the name is the same, you are declaring it in a different scope and it is evaluated according to that scope.

 
 
More Posts Next page »