Thursday, November 15, 2007

Create Custom BlackPearl SmartObject Service

1) Introduction

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

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

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

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

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

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

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

3) Summary Steps to Create

These are the summary steps to create a SmartObject Service:

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

4) Summary Steps to Update

These are the summary steps to update a SmartObject Service:

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

5) Detailed Steps to Create

5.1) Create Class Library

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

Add the following using statements:

using SourceCode.SmartObjects.Services.ServiceSDK;

using SourceCode.SmartObjects.Services.ServiceSDK.Objects;

using SourceCode.SmartObjects.Services.ServiceSDK.Types;

5.2) Create SmartObject Service

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


public class MyCustServiceBroker : ServiceAssemblyBase
{
public MyCustServiceBroker()
{

}
}

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

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

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


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

return base.GetConfigSection();
}

5.2.2) DescribeSchema()

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

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

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

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

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

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

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

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

this.Service.ServiceObjects.Add(so);

return base.DescribeSchema();
}

5.2.3) Execute()

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

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


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

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

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

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

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

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


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

5.2.4) Extend()

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


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

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

5.3) Build and Deploy It

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

5.4) Register the SmartObject Service

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

5.5) Create a Service Instance

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

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

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

5.6) Use the Service Method in a SmartObject

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

6) Detailed Steps to Update

6.1) Update the Service

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

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

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

6.4) Restart the K2 BlackPearl Service

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

6.5) Refresh Service Instances

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

7) Create a SmartObject using the Custom SmartObject Service

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

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

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

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

Wednesday, October 31, 2007

November K2 User Group Presentation

Update 4/16/2009 - Here is the recorded presentation

I will be presenting virtually to the K2.Net User Group Meeting Tuesday, November 13, 11am-1pm central time. The actually presentation will between 11:45-12:45 Central Time.

I plan to demonstrate how to build a simple custom SmartObject Service.

For information on how to attend, please go to the following - http://k2underground.com/forums/thread/19517.aspx

Sunday, October 28, 2007

K2 BlackPearl Initial Impression and 101 Training

Background

A few weeks ago K2 sent out an early version of their training VPC for BlackPeal. The training VPCs for K2.net 2003 were invaluable for building early iterations of K2.net processes, building up demos and proof of concepts, etc.

These notes are not comprehensive and will require some more research on my part as I get more acclimated with all the new features and functionality.

Notes and Improvements

The following are notes that I gathered while go through the initial set of the materials.

  • SmartObjects is the new big thing they have released and it pretty impressive. SmartObjects provide a way to access business data from your line of business databases. With K2.net 2003 you had to build up your own service layers and then integrate them into the processes. Now BlackPearl provides the services layer to integrate business data across the enterprise. There are two types of SmartObject. From my initial research the first are SmartObjects which the developer defines where the data is stored in the SmartBox sever. You can now very rapidly create data structures to persist your workflow data to. The second are SmartObjects which you can code up using their SDK which can load up from any disparate datasource available given you have a service of some type to work with. The data is not cached within SmartBox server, you are just building a proxy based on a start interface to retrieve data and surface it up through K2. This deserves a deeper dive and some best practices will develop with time.
  • BlackPearl has tried to make the creation of line rules easier with the addition of Actions. When building events you define actions that an end-user can select from. When the wizard is done BlackPearl will generate all of the lines which can be used to connect to other activities. This assumes is that conditional logic is somewhat simple; which is not always the case. In many cases K2 will create a dropdown for users to select these actions making your development of processes very quick. So if I had a process where a manager should be able to select approve, deny, or resubmit I would create those three actions and within InfoPath a dropdown will be added by K2 where those three values would appear (do not worry, it is not hard coded into the InfoPath form). These actions are also available everywhere throughout K2.net workspace, SharePoint web parts, etc. as context menu items allowing the user to take quick action on an event instance without having to open a form.
  • There was a neat new event called Forms Generation Event which is a client event handler to create very basic forms within the new K2.net workspace. Although, given how simple it was, I still need to test the boundaries of it to see how feasible it is to use with real heavy duty business requirements are thrown at it. Nothing replaces writing a start custom ASP.net page.
  • BlackPearl now provides a reporting solution which was not available in K2.net 2003. This was a major problem with K2 2003 and the solution was to persist all of your data externally to support reporting. Still K2.net is not the best place to store your business data and I still say it is best to keep that data externally (the new SmartObjects help with this best practice). Regardless all of the reporting within BlackPearl is now based completely on Reporting Services. They now provide several wizards to generate your own reports with the K2 Workspace. What was really exciting is that you can write your own custom reports in Reporting Services and then host them within the K2.net workspace. Reports associated to workflow may not always be K2.net centric and there may be multiple datasrouces where you need to retrieve data. Plus they have written their own data provider to access their data so you have immediate access to all data within K2 to create sophisticated reporting using Reporting Services. This is a huge improvement.
  • Users can now sign up for Notifications for processes, events, activities, destination rules, line rules, start rules, succeeding rules and escalations. This is great because with K2.net 2003 I continually had to design around not make K2 a spam machine. Now end-users are empowered with the ability to subscribe to events.
  • BlackPearl provides a web enabled process design tool that is embedded within SharePoint. The tool is better than the SharePoint Designer for creating standard business workflows as you can build workflows with InfoPath forms, they are re-usable, and easier to create custom activities. Still this tool is scoped toward created workflows within SharePoint only as it only is meant for designing workflows for a list or a library. I still need to test the boundaries for this tool. Given how easy it is to create workflows in Visual Studio using K2, I am more inclined to work with it at the moment.
  • BlackPearl now provides a way to create processes with Visio. To be honest when I heard about this at first was excited but skeptical it would be provide a way to create good workflows. During my initial testing of it turned out to work well and I was pleased with it. I will still need to again test the boundaries of this. The great thing about using Visio is that when defining the business process with business users during the elaboration phase I always use Visio to define the process. Although there is not always a one-to-one with the activities and events that I want to expose the business user to. So now if I create the workflows using Visio I can mark them up and them move them over to Visual Studio.
  • The integration with InfoPath 2007 has improved. Things to be excited about is the ability to support web enabled InfoPath forms, have multiple InfoPath forms in a single process, the InfoPath forms are integrated with Visual Studio along with your K2 process and the deployment of the InfoPath form with the K2 process (you no longer have to publish your InfoPath form, K2 will publish it for you). Although when adding .Net managed code to your InfoPath form has some problems due to the new MOSS requirement that any form with .Net manage code must be deployed through central administration. I will be doing a deeper dive into this soon.
  • K2.net provided a ton of integration with WSS 3.0 and MOSS. There are administrative event handlers for provisioning new sites, managing user permissions and groups and lists. These are very important as too often I go to companies where their SharePoint implementations have grown out of control because of no governance. Sometimes if the governance is too tight this can become a barrier for usage as business users cannot get what they need in a timely basis. These event handlers can be used to define light-weight business processes to automate the management of an enterprise MOSS implementation.
  • For lists and document libraries in SharePoint MOSS provides event handlers to do almost all of the operations you could need. Create, update, delete, check in/out, manage permission, move, copy, update metadata, download/upload documents, etc.
  • K2.net provides search event handlers that allow the developer to build up criteria to find items in SharePoint and then take action on them. I did not use reflector to go underneath the hood to see what it is being done but looks like these events are basically creating CAML queries that can go out and find items.
  • K2.net provides some new Records Management event handlers (create and delete records as well as put a hold on a record). My initial research into MOSS Records Management a few months ago I found that there is no workflow around the actual movement of documents into the records center. We have to rely on business users to go out, find a document and then move it to the records center. In many situations it is not that simple as there will be some sort of approval process before the document should be moved over. BlackPearl can be leveraged to build that business process.
  • BlackPearl provides several event handlers around managing web content. As you may know CMS has been rolled into MOSS. In MOSS this means new content management templates, publishing pages, etc. BlackPearl provides event handlers that can create, copy, move, delete, check in/out for publishing pages. As well you can get and update publishing page content, create pages using Conveters and create and Update reusable Content. I can see how this can be used extensively to create workflow around the management content.
  • BlackPearl provides integration with the BDC to expose SmartObject data. As you may know it can become difficult to create the XML configuration for the BDC to read data. If you are already loading in line of business data into your SmartObjects it is really almost no effort to expose any SmartObject through the BDC into SharePoint. It was really simple to hook up a SmartObject into the BDC as K2 has created some custom screens within SharePoint Central Administration to facilitate this. So if you have some SmartObjects with some Oracle, SAP and SQL data all you need to use the K2's BDC integration and you are done.

Miscellaneous Notes

  • Within Visual Studio they have introduced what they call environment configurations. They are similar to K2 2003 StringTable but are reusable between processes.
  • Improved presentation and integration with Visual Studio. Their implementation of WPF for their wizards is really clean and is exciting to see a real implementation of. They also implemented this neat little thing where the developer can draw letters on the Visual Studio canvas which will create a new activity. Is it necessary; not really; but it is neat.
  • The BizTalk events were not present on the VPC. I found out they have not be released yet. This is not terrible as it is still possible to invoke BizTalk through other means but I have used the K2 BizTalk adapters on two projects.
  • Getting into the properties of an event or activity is much easier now.
  • Workflows can be initiated directly from the workspace. In the past, we would have to write little stubs to kick off workflow.
  • The K2 Service Manager is now part of the K2 workspace. The K2 workspace is still geared towards the power user as it can still expose too much information to the common everyday business user.
  • They have created a new concept called "Roles" which seem to be similar to the Destination Queues which were available in K2.net 2003.
  • K2 deprecated a couple event handlers, the SQL and Web Service event handlers were gone. Good riddance J

Tuesday, October 16, 2007

MOSS Power User References and Training

Many clients have asked for references for not just a technical training but power user training and references for MOSS. This should compliment an earlier recommendation for technical references and training. I recommend picking up a copy of SharePoint 2007 User's Guide: Learning Microsoft's Collaboration and Productivity Platform by Seth Bates and Tony Smith.


This series so far has been really good for SharePoint 2007. The book lays out step by step instructions on how to do things. If you would like to read an introductory chapter please go here.


I would also highly recommend using the Office SharePoint 2007 Help and How to Site as there is a top of information here with steps on how to do every day sorts of things.


As for training I am going to again refer to MindSharp as they offer a SharePoint 2007 Power Users Course.

Monday, October 15, 2007

BlackPearl and MOSS Workflow Options

1. Background
A common question asked is should you use K2.net, Windows Workflow Foundation (WF) or SharePoint designer. The following is some quick things that should be considered.

1.1 General WF
WF architecturally has the look and feel of K2 and we know the K2 BlackPearl release is built completely on WF. Here is a high level context diagram of it.


A few things to keep in mind:


  • Have to build low level interfaces to move data between applications and the workflow definitions.
  • Have to build a hosting application to manage transactions (especially long-running transactions).
  • Have to create a transaction management and logging database.
  • Have to build ACID transaction management using WF activity library.
  • Must create graceful and generic exception handling.
  • No "out of the box" integration with any applications in the Microsoft stack.

This is not a comprehensive list and is based on observations and other projects I have seen try to do complete WF solutions from scratch. WF is great foundation to build solutions upon but it could be equated to "plumbing". If you have done work with BizTalk you may know it is a powerful Enterprise Service Bus pattern solution. BizTalk has orchestrations which would consistently confuse stakeholders into thinking that a simple diagram is drawn and configured and the project is done. We know this is not the case and the same analogy can be drawn for WF.

WF is similar in nature for the workflow world. It is very interface driven, requires lots of custom code and a high-level of effort. Some return on investment is gained with its activity library abstractions but not enough. As well, the level of adoption of pure WF solutions has not gotten off the ground when it comes to building custom applications.

1.2 SharePoint 2007 Workflow

There are two options when doing SharePoint workflow the first is building workflow in SharePoint Designer 2007 and building workflows in Visual Studio.

SharePoint Designer 2007 is a rebuild of MS FrontPage made specifically to work with SharePoint. Some disadvantages of building workflow in SharePoint Designer 2007 are:

  • Meant for business users as a code free workflow solution for managing items within SharePoint only.
  • When using SharePoint Designer 2007 your site can become customized (unghosted). Allowing business users to have elevated privileges to use SharePoint Designer 2007 is not good either.
  • Workflows are not reusable and are bound to the list (there are some known ways of doing it but it is not natural; the official answer is it is "NOT supported" - Porting SharePoint Designer Workflows to Visual Studio). Note this makes it difficult to move them between development, QA and production environments.
  • Limited conditional logic.
  • Cannot add custom code to workflow from designer (read whitepaper in called "Adding Activities to SharePoint Designer 2007" in the EMC Starter Kit).
  • Form integration not robust (ASP.net or InfoPath).
  • Centers around workflows to manage documents, sending emails and creating tasks only.
  • Supports sequential flows only.
  • No ability to debug.

The second option is to create custom WFs using the architecture discussed in the first section using Visual Studio. From information I have gathered from other colleagues whom have done pure MOSS WF solutions they we difficult. Some disadvantages of doing MOSS workflow in Visual Studio is:

  • Provide basic activities and events for MOSS only.
  • Difficult to deploy.
  • Robust audit and metric data must be built up.
  • Creating large multi-step processes difficult.
  • Integration with other platforms must be built from scratch.

1.3 References to Learn About Both
To try your hand at both SharePoint Designer and Visual Studio Workflow try this virtual lab.

As well check out the book I recommended here.

Also recommend reviewing these two links Workflow Development in Office SharePoint Designer and Workflow Development Tools Comparison.

2. K2.net BlackPearl and MOSS
K2.net BlackPearl simply becomes an obvious choice once you start pealing back the layers. It is built completely on top of WF and removes every one of the issues stated above. Out of the box:

  • Comes with event handlers to do every operation you need with a SharePoint List or Document Item. Updating metadata, moving, deleting, creating, batch operations, modifying the document permission, checking in/out, etc. They basically provide wizards to generate and customize Features requiring the developer to write less code.
  • They provide several events for publishing content supporting the Content Management Server (CMS) features that were rolled into MOSS.
  • They have some out of the box events to build workflow around Records Management.
  • Creating complex multi-stage InfoPath 2007 processes is supported (this includes web enabled forms).
  • Provide several events for SharePoint site administration. Events to managing users and permissions, managing lists/libraries and provisioning sites and workspaces, etc.
  • They provide an Ajax tool to build workflows on the SharePoint site itself.
  • Workflows can be authored inside of Visio.

There is really so much more as this is the tip of the iceberg for K2.net BlackPearl.

Saturday, October 13, 2007

Copy SPListItem.Version History with K2.net BlackPearl Part 2

1. Background

Copy Version History with SPListItemVersion to a new SPListItem Part 1 - http://k2distillery.blogspot.com/2007/10/copy-version-history-with_5.html

In a prior entry we were researching how to move the history from a SPListItem to another SPListItem that will be archived to another location. While doing research on this, we decided to check K2.net BlackPearl to see if it supported copying the version information. Using Reflector we were able to see that K2.net BlackPearl was not supporting the coping of version history when an SPListItem is copied from one list to another.

2. Resolution
The resolution is simple and still the same was as it would be done in K2.net 2003. Basically we need to go modify the code; the only difference is that we are now writing code in Windows Workflow Foundation (WF) instead of in an standard event handler.

Now go to the Event, right click on it and select to view the Event Item.

This will drive you to a screen where you will see the under laying WF code which BlackPearl is built on. Just seeing this is makes me giddy; even the underlying code is presented in a graphical format allowing developer to zero on the exact thing they need to modify. Writing code in this manner forces developers to write more modular code which is easier to understand and maintain.
There are two ways you can make this modification.

2.1 Option 1
One option would be to double click on CopyListItem code event handler and you will be taken to a code screen that you can start modifying.

2.2 Option 2
The second option would be to add an if statement and then add a new code event to the workflow. This is the best solution as it first segregates the custom code from the K2.net code. Second all of the K2 SharePoint events use their custom web services which we will not use. Instead we would insert code which was discussed in the part of this article series.

Copy SPListItem.Version (SPListItemVersion) Part 1

7/13/2008 - Going through and cleaning up code samples in popular posts. The old tool I used stunk. At least this is better. I apologize for the inconvenience.

10/14/2009 - I have created a new blog posting with updates as there were some issues with this original posting. Please go here.

1. Introduction

In MOSS you may have a requirement to take list items and move them to another location. A simple scenario would be that you have an item in a list and when a specific value is set you want to move that item to another location. One problem that you will run into is when you copy the item from one list to another is that you will want to ensure that the version history of the item is moved with the item. This article will discuss how you should go about fixing this problem; as well as a quick blurb on how this would be implemented in K2.net BlackPearl.

2. Available Methods and Approach

There are several available methods apart of SPListItem that you may be tempted to use. Specifically there are the CopyTo() and CopyFrom() methods. After some research online and using with Reflector to dive into the System.Microsoft.SharePoint neither of these methods can be used because the file name must be passed in has part of the URL parameters for the methods. With a standard list item object the file will be null making these methods useless unless you are working with a document object. As well, these methods do not support the copying of the version history.

The only viable solution is to loop over all of the values in SPListItemVersionCollection and set them into a new SPListItem. The Restore() method of SPListItemVersion cannot be used as it will take a specific version and restore it as a new version within the current SPListItem object. While looping over each SPListItemVersion the call Update() of the new SPListItem object will be called to rebuild the version history into the new object.

3. Solution Outline

An outline of the solution is as follows:

  • Create an Event Handler for the ItemUpdated event.
  • Create a new SPListItem object that will be the item that will be archived.
  • Loop over the Versions property of the source SPListItem. When looping over the item you must loop going backwards as the last item in the SPListItemVersionCollection is the first version saved.
  • Set each field from SPListItemVersion and call update on the new SPListItem (if there are 50 versions, then update will be called 50 times).
    • Note that index zero is always the latest version that a user would edit through SharePoint.
  • Then move the file attachments to the new SPListItem.
    • Note that attached documents to a SPListItem are not versioned. If a document needs to be versioned, it is best to put that document in a document library that is versioned controlled and then link the document to the SPListItem.
  • Deploy the Event Handler as a Feature.

4. Code

4.1 Feature

This article will not discuss the particulars of creating Features but shows them here for reference. NOTE – Change the GUID

<FeatureId="FB7D9BC1-95EA-43fc-85A7-AE6B0123E397"
Title="Item Archive Event"
Description="This event will archive an item when it is closed"
Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifestLocation="Elements.xml" />
</ElementManifests>
</Feature>

4.2 Elements of Feature

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ReceiversListTemplateId="100">
<Receiver>
<Name>ArchiveItemEventHandler</Name>
<Type>ItemUpdated</Type>
<SequenceNumber>1000</SequenceNumber>
<Assembly>
Events, Version=1.0.0.0,culture=neutral,
PublicKeyToken=419549f693d50b9c
</Assembly>
<Class>Events.ArchiveItemEventHandler</Class>
</Receiver>
</Receivers>
</Elements>

4.3 EventHandler Class

public classArchiveItemEventHandler : SPItemEventReceiver {

public overridevoid ItemUpdated(SPItemEventProperties properties) {
ArchiveItem(properties);
base.ItemUpdated(properties);
}
}

4.4 Create Folder

This solution is based on moving items to an archive folder which is dynamically generated every month. The code below creates a new folder in SharePoint based on the current month.


private SPListItem GetArchiveFolder(SPList list) {
//Get the folder for the current month
string folderName = System.DateTime.Now.Month.ToString() +
System.DateTime.Now.Year.ToString();

SPListItem destinationFolder = null;

foreach (SPListItem folder in list.Folders) {
if (folder.Name == folderName) {
destinationFolder = folder;
break;
}
}

if (destinationFolder == null) {

//Create new folder
destinationFolder = list.Items.Add(
list.RootFolder.ServerRelativeUrl,
SPFileSystemObjectType.Folder, null);

if (destinationFolder != null) {
destinationFolder["Name"] =
"Archive Closed Records (" + folderName + ")";
destinationFolder.Update();
}
}

return destinationFolder;
}

4.5 Copy the Versions into a new Item

This code is the meat of the solution. Couple of more notes:

  • A new archive item SPListItem is created in the archive folder location.
  • The DisableEventFiring() and EnableEventFiring() methods wrap the code. This is needed because the there will be an Update() call for every version.
  • Note again that the looping of the versions starts with the last item in the collection and works backwards.
  • There is code to accommodate Created, Created By, Modified and Modified By. They are read only fields. If they are not set the values will be lost and set with the system account and the time in which the code executed.
  • After all of the versions have been updated into the new item, the attachments are moved into the new item.
  • Finally the source item is deleted.

That is essentially it. The biggest thing to walk away with is that the properties.ListItem.Version[x][y] is two dimensional and that is what gets you access to all of the field values in the version collection.


private void MoveItem(SPListItem sourceItem,
SPListItem destinationFolder,
string newItemLocation) {

//Create a new item
SPListItem archiveItem = destinationFolder.ListItems.Add(
newItemLocation,
sourceItem.FileSystemObjectType);

DisableEventFiring();

//loop over the soureitem, restore it
for (int i = sourceItem.Versions.Count - 1; i >= 0; i--) {
//set the values into the archive
foreach (SPField sourceField in sourceItem.Fields) {
SPListItemVersion version = sourceItem.Versions[i];

if ((!sourceField.ReadOnlyField) && (sourceField.Type != SPFieldType.Attachments)) {
archiveItem[sourceField.Title] = version[sourceField.Title];
}
else if (sourceField.Title == "Created"
sourceField.Title == "Created By"
sourceField.Title == "Modified"
sourceField.Title == "Modified By") {

archiveItem[sourceField.Title] = version[sourceField.Title];
}
}

//update the archive item and
//loop over the the next version
archiveItem.Update();
}

//now get the attachments, they are not versioned
foreach (string attachmentName in sourceItem.Attachments) {
SPFile file = sourceItem.ParentList.ParentWeb.GetFile(
sourceItem.Attachments.UrlPrefix + attachmentName);

archiveItem.Attachments.Add(attachmentName, file.OpenBinary());
}

archiveItem.Update();

EnableEventFiring();

//Now delete the current item.
sourceItem.Delete();
}

5. Referencs