Delivering Modular SharePoint Workflow Functionality (Part 2 of 2)

Summary: Examine the options, process, and benefits of adding support for SharePoint workflows to your application, and for breaking that support into components that clients can use to build their own workflows. This article is part 2 of 2. (17 printed pages)

David Mann, Mann Software, LLC

June 2008

Applies to: 2007 Microsoft Office system, Microsoft Office SharePoint Server 2007, Microsoft Office SharePoint Designer 2007, Microsoft Visual Studio 2008 development system, Visual Studio Tools for Office (3.0)

Download the code sample that accompanies this article: Sample Code: Delivering Modular Workflow Functionality in SharePoint Server 2007

Read Part 1: Delivering Modular SharePoint Workflow Functionality (Part 1 of 2)

Contents

  • Creating an Action

  • Packaging the Activity or Action for Deployment

  • Encapsulating Business Logic

  • Extending Your API

  • Packaging for Deployment

  • Building Our Feature

  • Using STSDev

  • Conclusion

  • Additional Resources

Creating an Action

In Delivering Modular SharePoint Workflow Functionality (Part 1 of 2), we created an activity that will work great in Visual Studio, but that will not work in Microsoft Office SharePoint Designer 2007. So we're only part way to our goal. Fortunately, creating a custom action for SharePoint Designer is a simple configuration process. We need to write some XML, but no code.

The first thing we do is add an entry to the web.config file so that Windows SharePoint Services or Microsoft Office SharePoint Server 2007 know about our activity and how to load its assembly. This is very similar to adding a SafeControls entry for a custom Web Part.

Note

I'll explain the entry we need to make here, and then walk through how to make it manually in our development environment. However, before we can package our activity or action for deployment, we need to ensure that our installation routine adds the proper entry to the client's server. In Additional Resources, I'll mention some tools that make the whole process much easier.

Packaging the Activity or Action for Deployment

Open the web.config file. Near the end of the file, you'll see a section named <System.Workflow.ComponentModel.WorkflowCompiler>. Immediately under that, you'll see a section named <authorizedTypes> that contains <authorizedType> child tags. Add a new <authorizedType> tag for our new action, as shown in the following example.

<authorizedType Assembly="CaseTrak.Activities, Version=1.0.0.0, 
  Culture=neutral, PublicKeyToken=83d08a9b74a6ff8a" 
  Namespace="CaseTrak.Activities" TypeName="*" Authorized="True" />

Remember you must replace the PublicKeyToken value with the value from your strong name key or else this will not work.

Developers who are comfortable creating custom Web Parts will recognize that this is nearly identical to a SafeControls entry. Someone who is not familiar with a SafeControls entry should still be able to figure out the following pieces:

  • Assembly: The full four-part name for your assembly, easily retrieved from a tool such as Reflector.

  • Namespace: The namespace that is specific to your activity. For our sample scenario, this is CaseTrak.Activities.

  • TypeName: Typically contains an asterisk to denote "all types"; however, you can list your specific class.

  • Authorized:True to make your activity "safe"; otherwise, false, which seems like a waste of a web.config entry.

Now that we've told SharePoint Products and Technologies about our action and that it's okay to run it, we need to tell SharePoint Designer about it. This is also easy and involves writing only some light XML. Locate the path C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow. You should see a file named WSS.ACTIONS in this folder; this file contains a wealth of information about custom SharePoint Designer actions. Within this file, you will find an example of everything that is possible using SharePoint Designer actions.

Unfortunately, the file is over 700 lines in length, and though it is only XML, it is a little painful to wade through all of the options just to determine what you need to know to build an action. In this article, we'll look at the more common options for building custom actions. For a deeper look at all of the options (including a schema definition), see Additional Resources.

The first thing you need to know about the WSS.Actions file is that like any other SharePoint default file, you should never modify it. Instead, you must make your own ACTIONS file in the same directory. Windows SharePoint Services and SharePoint Server will read your file and load your custom actions into memory with the default actions. To do this, simply create a file in the same directory, name it something meaningful, and make sure that it has an .ACTIONS extension. For our example, we will name the file CaseTrak.ACTIONS.

For our sample, simply add a text file to your solution in Visual Studio and add the XML to it. Later when we discuss packaging and deploying, we'll handle getting this copied to the proper place.

The following code example shows the full contents of our CaseTrak.ACTIONS file. Though the contents are fairly self-explanatory, the Table 1 provides more detail.

<?xml version="1.0" encoding="utf-8"?>
<WorkflowInfo Language="en-us">
<Actions  Sequential="then" Parallel="and">
      <Action   Name="Update CaseTrak Status"
            ClassName="CaseTrak.Activities.StatusUpdater"
            Assembly="CaseTrak.Activities,
Version=1.0.0.0,Culture=neutral,
PublicKeyToken=bf813961d1f833f1"
            Category="CaseTrak Actions"
           AppliesTo="all">
          <RuleDesigner Sentence="Set CaseTrak Status for %1 to %2">
            <FieldBind Field="CaseID" Text="this Case"
DesignerType="string" Id="1"/>
            <FieldBind Field="CTStatus" DesignerType="Operator" 
OperatorTypeFrom="DropDownMenu" Id="2" >
                <Option Name="Open" Value="1"/>
                <Option Name="Closed" Value="2"/>
                <Option Name="Pending" Value="3"/>
                <Option Name="Deferred" Value="4"/>
                <Option Name="InProcess" Value="5"/>
            </FieldBind>
          </RuleDesigner>
        <Parameters>
        <Parameter Name="CaseID"   Type="System.String, mscorlib"
Direction="In" />
        <Parameter Name="CTStatus" Type="System.Int32, mscorlib"
Direction="In" InitialValue="5" />"</Parameters>
    </Action>
  </Actions>
</WorkflowInfo>

Table 1. Elements in the CaseTrak.ACTIONS file

Element Notes

Action

Each action in our file has its own Action element, which explains the details for that action. The ClassName and Assembly attributes are self-explanatory. Category is straightforward, and as shown in the example, you can specify your own value to create your own category within the SharePoint Designer Action dialog box. The last attribute, AppliesTo, determines which type of list the action can be used against. Possible values are list, doclib, or all.

RuleDesigner

SharePoint Designer workflows are based on the concept of sentences that describe the action to take. For example, a sentence could read "Send an Email to the document owner". This is more user-friendly than some other possible constructs. In SharePoint Designer, the sentence for our activity will read "Set CaseTrak Status for case <CaseID> to <Status>" where <CaseID> and <Status> are parameters (represented here by %1 and %2) which can be replaced with actual values by the workflow builder.

FieldBind

Each parameter (%n) from the sentence for our action is bound to a field, which specifies the name of the field, the text shown until the parameter is assigned a value, the type of Designer shown when setting a value for the parameter, and the ID of the field. The first Field uses a simple string Designer; the second uses a slightly more complex drop-down designer. For details on possible DesignerType values, see Additional Resources.

Parameter

Each field in the example requires a corresponding Parameter section. The Parameter entry provides details on how the data from the field is handled. The Name of the parameter must match a public property in the Action assembly. The other important attribute here is the Direction attribute. This specifies whether the parameter passes a value to the assembly (In), receives a value from the assembly (Out) or is optional (Optional)—which only applies to In parameters. As seen in the second parameter from our example, you can also specify an Initial value via the InitialValue parameter.

With those elements in place, all we need to do is reset IIS or recycle our SharePoint Application Pool and our new action becomes available in SharePoint Designer, as shown in Figure 10.

Figure 10. Custom action viewed in the SharePoint Designer list of actions

Custom action viewed in the SharePoint Designer

Now, when a user selects our action for use in a workflow, they see something similar to what is shown in Figure 11.

Figure 11. Initial view of the custom action

Initial view of custom action

As we saw in our ACTIONS file, the Case and InProcess are parameters that must be replaced with actual values. The former is a simple string, and the latter a drop-down list of valid choices, as shown in Figure 12.

Figure 12. Parameters specified for the action must be replaced with valid values

Valid values for parameters specified for action

With that, our custom action is finished. All we need to do to complete its functionality is to create a custom condition for use in SharePoint Designer. We'll address that next.

Creating a Condition

A custom condition is very similar to a custom action. The major difference is that conditions typically are not going to start as activities or other assemblies because they are a slightly different. The process of building a condition, however, is largely the same as building an action:

  1. Build a custom assembly that encapsulates your logic.

  2. Deploy the assembly.

  3. Register and configure the condition so Windows SharePoint Services or SharePoint Server is aware of it

With that said, there is no reason not to, and many reasons to, include the code necessary to support a condition in the same assembly as your action. It makes things easier. If you are not developing actions, however, know that a condition can exist in any assembly. I'll provide the details of what needs to be added to the assembly shortly.

Continuing in the same scenario as our action/activity, our condition is part of Contoso Software's CaseTrak application. In this case, we need to be able to process case reviews only when a particular case is not on an administrative or legal hold. Therefore, this condition must make a call back into CaseTrak to check the hold status of each case. I'll walk you through the process here.

In code, a condition is simply a method that returns a Boolean value. To keep things simple, we're going to add that method directly to our Activity assembly. The important points for this method are that it must be static and that it must return a Boolean value. Other than those two items, you can implement your method in any way that properly calculates a Boolean return value.

In the example code included with this article, we simply return true in all cases. In a real system, we would pass the CaseID parameter to a CaseTrak Web service and get back a value that tells us whether or not the Case is on an administrative or legal hold. In either case, we return that value back to the workflow and everything continues processing appropriately.

The last step in configuring our condition is to register the condition so that Windows SharePoint Services or SharePoint Server, and therefore SharePoint Designer, are aware of it. Again, we do this by adding some simple XML into our ACTIONS file, just below the root WorkflowInfo element.

<Conditions  And="and" Or="or" Not="not" When="If" Else="Else if">
    <Condition   Name="Case Not On Hold"
           FunctionName="CaseNotOnHold"
           ClassName="CaseTrak.Activities.StatusUpdater"
          Assembly="CaseTrak.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bf813961d1f833f1"
       AppliesTo="all"
             UsesCurrentItem="false">
      <RuleDesigner Sentence="Case %1 is not on Hold">
        <FieldBind Id="1" Field="_1_" Text="CaseID"/>
      </RuleDesigner>
      <Parameters>
        <Parameter  Name="_1_" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Condition>    
</Conditions>

The elements in this XML are mostly identical to what we described earlier, so we won't go into details again here. As before, there are many options for Fields and Parameters elements. See Additional Resources for more information.

With these elements in place, you reset IIS or recycle your Application Pool, and then open SharePoint Designer. You can now use the new condition, as shown in Figure 13.

Figure 13. Custom condition

Custom condition in action

Note

Depending on factors such as the nature of your change and how your server is configured, simply recycling your SharePoint Application Pool might not be sufficient because SharePoint Designer caches information. If you are making changes, especially to your ACTIONS file, and are not seeing the changes reflected in SharePoint Designer, try executing an iisreset command, and then close and reopen the site in SharePoint Designer. If that still doesn't work, you must manually clear the SharePoint Designer cache. To locate the cache file, close SharePoint Designer, navigate to the path Documents and Settings[User_Name]\Local Settings\Application Data\Microsoft\WebsiteCache and delete the folder named for your site.

Summary of Customization Options

SharePoint workflow, by virtue of being built on top of Windows Workflow Foundation (WF) is extremely flexible and extensible. By default, there is significant value for building generic human-centric workflows. The benefit to an independent software vendor (ISV) is the ease with which the power of WF can be extended to integrate with their product. There is no reason to look elsewhere or build a workflow engine for your product if integrating with SharePoint is an option for you and your customers.

Whether workflow integration with your product will be built using SharePoint Designer or Visual Studio is not a decision you have to make. As an ISV, it is important for you to support both tools, for several reasons, as follows:

  • Supporting SharePoint Designer but not Visual Studio is clearly not a viable option as you have to create a Visual Studio activity to create a SharePoint Designer action.

  • Because it takes a small amount of effort to add SharePoint Designer support, there is little reason to support only Visual Studioand much to gain.

  • By supporting both tools, you keep your client's options as open as possible. You are not forcing them to adapt to any one paradigm.

Encapsulating Business Logic

So far, we've walked through the technical aspects of how our customers will use the components we give them. It's important to start with this understanding so that we know what is possible technically. Now, however, we need to turn our attention to our own software product. We need to understand the processes of our application and be able to identify where it makes sense to carve out pieces of functionality to expose as components. While it might very well be that we could expose every piece of functionality offered by our application, it might not make sense to do so.

Unfortunately for this article, each application, situation, and ISV is going to be different. This will make it difficult to present hard-and-fast rules as to how you should approach the process of exposing your application as components upon which to build a process. Instead, we provide some general rules, discussing the types of things you need to think about.

First and foremost, it is important to understand how your application can be used, and its common usage scenarios.. This understanding might require you to interact with whoever handles sales and marketing or market research for your company. To get you started, here is an idea of the types of questions you need to ask:

  • What information from our application would users like to expose to another application in their environment?

  • Are there other applications in use at client sites that offer services that are complementary to our application that could be integrated to create a composite application or process?

  • Are there other applications or processes in the client's environment that could feed or be fed by our application?

  • Are there pieces of our application or process that users do not make use of?

You might also be able to get some of this information from whoever is responsible for installing and supporting your application into client environments.

After you have answers to questions such as these, you need to do a technical assessment of your application. You must understand the pieces of the application that are candidates for exposure. If one portion of your application, for example, had such unique or stringent security or auditing requirements for editing the content, you might not want to expose it for editing. In this case, it might be possible to simply offer read-only access to those pieces.

In another scenario, part of your application might need to handle large amounts of data. Exposing this content to be extracted and pulled into another application might not be feasible. Similarly, certain pieces of content might be valid only in relation to other pieces of similar content. In this case, it might not make sense to expose the content as individual elements.

The last scenario we'll examine is the idea of preserving proprietary business concepts or algorithms. If exposing pieces of your application as a modular component would reveal the inner workings to a client or competitor and diminish or eliminate your competitive advantage, it obviously would not make good business sense to do so. In that case, you can choose to prevent those elements from being exposed, or can break them out in such a way as to minimize any possible threat. You can still protect proprietary information in your application, and expose only inputs and outputs that keep your proprietary elements as a black box.

Extending Your API

It was once safe to say that there were two primary methods to extend your .NET-based product's API:

  • By using Web services

  • By using .NET Remoting

Which method you chose depended on several factors. If you chose Web services, you traded decreased performance for cross-platform support. If you chose .NET Remoting, you traded cross-platform support for increased performance. With either option, we can keep to a services-based architecture by maintaining the separation of our core business logic from the external processes that will access our API.

At a high level, the options are functionally equivalent. Each allows for an application or process running in one application space to make calls into our application by using our defined API, to run in a different application space and potentially on a completely different server. This means that we do not need to install any piece of our core API anywhere but on the server that is running our application. It also means that we have defined a contract and agreed to support it. At any time in the future, we can make any change we want or need to our application, and as long as we do not break the defined contract, we do not need to worry about breaking any application that is using our API. We can also add new functionality to what is specified in the contract provided that we do not change the existing functionality.

Over the last several years, improvements in the performance and security of Web services have led to their being declared the winner of the battle. Rarely does anyone use .NET Remoting anymore; however, if that is the direction in which your API extensibility has gone, rest assured that nothing here prevents you from continuing down that path.

From our point of view, this simplifies things considerably: we extend our API by wrapping Web services around it. Any functionality that needs to be exposed to the outside world gets a Web service interface. We define the contract—the inputs and outputs—and any application that can interact with Web services can immediately begin integrating with our product.

From an ISV's point of view, that sounds pretty attractive. And the best part for this article is that there is absolutely nothing that you need to do differently to enable the workflow components you build to call into your Web services. Craft your Web services as you need to for the rest of your application and let the components call them as they need to.

Packaging for Deployment

Technically, packaging our components for deployment could easily be handled differently from packaging most other SharePoint components. There is nothing specific to Windows SharePoint Services or SharePoint Server about most of what we've discussed. However, we are in fact developing for a SharePoint environment and know that our components will be deployed to a SharePoint environment. So it behooves us to keep SharePoint Products and Technologies in mind as we prepare for deployment.

Because our components will be installed with various other SharePoint components, our deployment should look and feel like any other SharePoint deployment. This means we need to consider Features and solutions. There are several approaches to building out your Feature and solution; the approach I'll take here is one that seems to be emerging as the most common.

Before we get to that, let's review exactly what it is we need our deployment process to do for our components. Our deployment process must:

  1. Install our assembly to the global assembly cache.

  2. Copy our ACTIONS file to the path Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow.

  3. Update the web.config file with an authorizedType entry.

We can easily do all of this by using solutions and Features:

  • Solutions excel at physically moving files, so we'll use a Solution to handle the first two tasks.

  • Features allow us to execute code when they are activated or deactivated, so our Feature will handle the third task. One caveat is that our components are targeted at a level higher than an individual Web or site collection, and we want someone with some level of administrative permissions to manage these components. This means that we will scope our Feature to either a Web Application or a Farm level. You must decide which is appropriate for your situation or expect to allow the person who installs your application to decide which is appropriate for their environment.

Now let's build our Feature.

Building Our Feature

The Feature we build is going to be slightly different from many others that you have probably created, as we will handle all of the work for our Feature in the Feature Receiver. The Feature itself does nothing, so we won't have an elements file, as we'll see shortly. Before we go too far, recall from our earlier list of functionality that the Feature will handle the last task: Update the web.config file with our authorizedType entry. Now let's build the Feature Receiver.

Building a Feature Receiver

A Feature Receiver is nothing more than a class that inherits from the SPFeatureReceiver class, and implements the event methods we need. In our case, we need only the event receiver for the FeatureActivated event, as shown in the following code.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWebApplication wappCurrent = (SPWebApplication)properties.Feature.Parent;
SPWebConfigModification modAuthorizedType = new SPWebConfigModification();
modAuthorizedType.Name = "AuthType";
      modAuthorizedType.Owner = "CaseTrak.Actions";
      modAuthorizedType.Path = "configuration/System.Workflow.ComponentModel. WorkflowCompiler/authorizedTypes";
      modAuthorizedType.Type = SPWebConfigModification.SPWebConfigModificationType. 
      EnsureChildNode;
      modAuthorizedType.Value = @"<authorizedType Assembly="" CaseTrak.Activities, Version=1.0.0.0, 
        Culture=neutral, PublicKeyToken=bf813961d1f833f1"" 
        Namespace=""CaseTrak.Activities"" TypeName=""*"" 
        Authorized=""True"" />";
      wappCurrent.WebConfigModifications.Add(modAuthorizedType);
      wappCurrent.WebService.ApplyWebConfigModifications();
        }

If you recall, we need our Feature Receiver to add a child node to the configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes element within our web.config file. This code does this by working with the SPWebConfigModification class. All we're doing is specifying the path within the web.config file that we need to add our element to, and then the value of the child node we need to add.

<authorizedType Assembly="CaseTrak.Activities, Version=1.0.0.0, 
  Culture=neutral, PublicKeyToken=bf813961d1f833f1" 
  Namespace="CaseTrak.Activities" TypeName="*" Authorized="True" />

There is no reason not to add this class to our Activity assembly, as both must be deployed to the global assembly cache. You'll see in the example code that this is exactly what I did. Compile the assembly and we can move on to the rest of the Feature elements.

Feature.XML

Feature.xml is the SharePoint file that describes your Feature. This structure of this file is mostly the same for each Feature you create, differing only in the actual values for the elements. Create a blank XML file (just in the root of your Solution for now, we'll move it in a few minutes), and then add the appropriate entries.

<?xml version="1.0" encoding="utf-8" ?>
<Feature
  Id="AB37234F-3EA1-DD38-AA21-652A894BC21A"
  Title="CaseTrak Workflow Components"
  Description="Install and activate components for use in Workflows"
  ImageUrl="CaseTrakLogo.jpg"
  Scope="WebApplication"
  Hidden="false"
  ReceiverAssembly="CaseTrak.Activities, Version=1.0.0.0, Culture=neutral,Ã PublicKeyToken=bf813961d1f833f1"
  ReceiverClass="CaseTrak.Activities.FeatureReceiver"
  xmlns=https://schemas.microsoft.com/sharepoint/>
  <ElementManifests />
</Feature>

As always, you need to update your PublicKeyToken value.

Table 2 describes the elements of the Feature.xml file.

Table 2. Elements in the Feature.xml file

Element Name Description

Id

A GUID the uniquely identifies this Feature.

Title

The name for the Feature, as it will appear in the user interface (UI).

Description

The descriptive text that will appear under the Title in the Feature list.

ImageUrl

The path to an image that will be shown in the list of Features with your Feature name. This path is relative to your Feature directory. You can make your components look more professional by putting your company or product logo here.

Scope

The location this Feature will be deployed to. While valid options for Features are Farm, WebApplication, Site, or Web, notice that we will restrict this to WebApplication because we want to control who can manage the availability of our components. Setting this to WebApplication or Farm will require that an administrator have permissions at the Web application or farm level respectively. This makes things a little more secure for our components.

Hidden

A Boolean value to indicate whether the Feature is visible in the UI.

ReceiverAssembly

The four-part name of the assembly that contains our Receiver. This does not have to be the same assembly as your components, but that's how we do things in this example.

ReceiverClass

The name of the Receiver class within our assembly.

Xmlns

The XML namespace for the schema. For SharePoint Server 2007, this is always https://schemas.microsoft.com/sharepoint/.

ElementManifest

Element that contains the location of the element manifest file. The element is required, but we're not making use of an element manifest file, so we can leave it blank.

That takes care of the Feature and Feature Receiver; now let's package it all into a solution.

Solutions

Building a SharePoint solution is not a difficult process, but it is very exacting. You need to set up all of the pieces exactly to build the .wsp (solution) file correctly, and to enable correct deployment of your solution. We'll walk through the process of building the .wsp file at the end of this section. Now, it is important to understand the purpose and function of a solution package and to look at the elements that compose it: the manifest file and the directive (.ddf) file.

Solution packages are simply a mechanism for deploying new functionality to Windows SharePoint Services or SharePoint Server. However, they provide great benefits for deployment. If you want your workflow components to look and act their best in your application, you have to use them. Let's look at the process.

You can build your solution in two ways: manually or by using tools that automate the process. Normally, I'm a big fan of tools because they can save you a lot of time and effort. However, I think it is more important to understand what those tools are doing for you, so we'll examine the manual process here. In Additional Resources, I provide some links to a few tools that can take a lot of the pain out of this process.

Note

We will focus on building a solution to address the needs of the components in this article. For a great article about how to build building solutions in general, see Ted Pattison's August 2007 Office Space column in MSDN Magazine, Solution Deployment with SharePoint 2007.

Manifest File

The manifest file defines the solution and lists all of the elements that make up the solution. Like Feature.xml and Workflow.xml, the manifest file for your solution is an XML file, this time adhering to the Solution Schema. A sample manifest file looks like the following.

<?xml version="1.0" encoding="utf-8" ?>
<Solution xmlns="https://schemas.microsoft.com/sharepoint/"
  SolutionId="79deac59-22ab-da1e-9d7a-1732d31cbe07" >
  <Assemblies>
    <Assembly DeploymentTarget="GlobalAssemblyCache"
Location="CaseTrak.Activities.dll" />
  </Assemblies>
  <TemplateFiles>
    <TemplateFile Location="1033\Workflow\CaseTrak.ACTIONS"/>
  </TemplateFiles>
  <FeatureManifests>
    <FeatureManifest Location="CaseTrak_Activities\Feature.xml"/>
  </FeatureManifests>
</Solution>

Table 3 provides details about the elements of the Manifest.xml file.

Table 3. Elements of the Manifest.xml file

Element Description

SolutionId

A GUID that uniquely identifies your solution.

Assemblies

Specifies to add one node for every assembly that is part of your solution.

DeploymentTarget

Tells the solution installer where to put the DLL. Valid options are WebApplication or GlobalAssemblyCache.

Location

Tells the solution installer where to find your DLL.

TemplateFile

In this example, tells the solution installer where to find our ACTIONS file within the .wsp file, and also where it needs to be placed on the file system. The path here is relative to the TEMPLATE folder.

FeatureManifest

Tells the Solution installer where to find your Feature.xml file.

That's all we need for this example manifest file. There are quite a few other options available. If you're curious, you can review the schema file in Solution Schema.

Directive (.ddf) File

The .ddf file is the final piece necessary for building our solution. This is the file that is used to define the structure of our solution file. During the process of building our .wsp (solution) file, the MakeCAB utility (more on this in a moment) reads the .ddf file and packages all of the files for as specified in the .ddf.

Note

Although our solution file has a .wsp extension, it is really nothing more than a standard .cab file with a different extension. If you change the extension back to .cab, you can open the file in Windows Explorer and examine the contents.

Our sample .ddf file looks like the following.

;*************
; This .ddf file specifies the structure of the .wsp solution cab file. 
;*************

.OPTION EXPLICIT
.Set CabinetNameTemplate=CaseTrak_Activities.wsp
.Set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=Package

manifest.xml manifest.xml
bin\debug\CaseTrak.Activities.dll CaseTrak.Activities.dll
RootFiles\TEMPLATE\FEATURES\CaseTrak_Activities\feature.xml
CaseTrak_Activities\feature.xml
RootFiles\TEMPLATE\1033\Workflow\CaseTrak.ACTIONS
1033\Workflow\CaseTrak.ACTIONS

The top of the file sets up some variables that configure how the .wsp file is generated and are generally self-explanatory. Table 4 describes the elements in the .ddf file.

Table 4. Elements in the .ddf file

Element Description

CabinetNameTemplate

Specifies the name of the resulting .wsp file.

DiskDirectoryTemplate

Places all generated .cab files in a single directory

CompressionType

Specifies the algorithm used for compressing files.

UniqueFiles

Indicates that all files in the .cab file must have unique names.

DiskDirectory1

Specifies a subdirectory into which .cab files are placed.

Typically, the top part of the .ddf file is the same (except for the CabinetNameTemplate parameter) for all solutions that you build.

The rest of the file is unique to the specific solution that you are building. It specifies the files that will be packaged into the .wsp file, and the following:

  • The first part of each line is the source location so that MakeCAB can read them to add them to the .cab file.

    Note

    This location is relative to the current directory, which will typically be the location of the .ddf file or the location of the MakeCAB utility.

The second part of each line specifies the destination location of each file within the .cab file. In the previous example:

  • The Manifest.xml file and our DLL will be located in the root of the .cab file.

  • The Feature.xml file will be in the root of our Feature (CaseTrak_Activities) folder.

  • The ACTIONS file will be in a folder path 1033\Workflow. Remember that when the solution is deployed, this location is referenced in the TemplateFile element of Manifest.xml and is relative to the \Template folder.

For the files portion of your .ddf file, it is important that you include each source/destination pair on one line. They are broken onto two lines in our listing here only for readability.

Note

It is an emerging best practice to build a folder structure within your Visual Studio solution that mirrors the structure of the SharePoint 12 folder (often referred to as the "WSS Root" or "12 hive"). While not required, this is the approach we have taken here. In addition, the various tools recommended in Additional Resources follow a similar approach.

To understand the file system structure that is required for this .ddf file to work properly, examine Figure 14. It shows a view of our project with all of the folders and files laid out as the DDF and MakeCAB utility expects to find them. You'll need to recreate this file structure within Visual Studio and copy the files to the appropriate places. Notice that the Utilities folder contains a few batch files for building and testing the solution file and the MakeCab.exe application. See Additional Resources for information on how to download a copy of MakeCAB.exe, as part of the Microsoft Cabinet SDK. The remainder of this solution is available in the source code download for this article.

Figure 14. Directory structure required for solution

Directory structure required for solution

With that, the .ddf file is complete. We can move on to building the .wsp file.

Building the Solution File

The last step is to actually build the solution (.wsp) file. We've discussed how to construct the files in our solution, and the file structure we need; now we can create the solution file. Because we manually built our Manifest and Directive files, we can't take advantage of the functionality provided by the PostBuildActions.bat file provided by the Microsoft Office SharePoint Server 2007 SDK, which is okay because we wouldn't have it available if we were just using Windows SharePoint Services or SharePoint Server anyway. This means that we need to support only one process, regardless of which SharePoint version we are using.

Building the .wsp file can be handled easily with two lines in our MakeCAB.cmd file (available in the source code download).

cd ..
Utilities\MakeCab /F CaseTrak_Activities.ddf

After running this batch file, your .wsp file is available in the DeploymentFiles\Package directory within your project folder. If you'd like to take a look at the contents of the .wsp file, simply change the extension to .cab and open it in Windows Explorer. Just make sure to change the extension back when you are finished.

Wrapping Up Deployment

As mentioned at the beginning of our deployment discussion, it is important that the deployment process for your workflow components look professional and like it belongs with the rest of your application deployment. Because of this, the typical batch files used for deployment (including the Deploy.cmd and the UnDeploy.cmd files in the source code download, which are good for testing your solution package) will not suffice. Instead, you must look into something a little more polished. See Additional Resources for links to a few options.

We described the manual way of doing things here so that you understand what is happening. I strongly recommend that you open the three .cmd files in the source code download and review the steps they perform. This will help you understand what is happening and, when you start using a tool, to understand and appreciate what the tool is doing for you.

Using STSDev

Now that you've made it this far and have a good understanding of what it takes to build custom activities, actions, and conditions, I have some good news for you. You don't need to go through all of that pain again each time you want to build a new one. There is a community-developed tool available on CodePlex (see Additional Resources for the direct link) called STSDev. It includes an option to select some options in a wizard-like interface and then have the tool generate a complete activity, action, or condition with everything you need, as follows:

  • All five Activity classes with stub methods in place

  • Your ACTIONS file

  • A Feature Receiver to add the AuthorizedType entry to the web.config file

  • All deployment files (Feature.XML, Manifest.XML, .ddf, and .wsp).

I strongly recommend that you use this tool. It will save you time and aggravation.

Conclusion

Whew. This was a long process, but we've succeeded. That's it, we're finished. We started this article with a somewhat vague idea of what we were getting ourselves into, with a lot of hope and a little fear that things were going to be difficult. But here at the end, we discover that things really aren't so tough after all.

Along the way, we managed to sidestep the argument of whether to support SharePoint Designer or Visual Studio by realizing that we need to support both. We learned about activities for Visual Studio and actions and conditions for SharePoint Designer, and described the process of building all three AND making them stand out from the run-of-the-mill components that lesser developers might create. Finally, we closed out with how to package our components so that they can easily be deployed along with the rest of our application with all the spit and polish of a professional installation. Not too bad for an afternoon's work.

In all seriousness, the ability to extend SharePoint workflows is one of the most exciting features of SharePoint as an application platform and one that ISVs should look at quite seriously. By extending SharePoint workflow, you can easily build upon an enterprise-class workflow engine to bring that power and reach to your application. The SharePoint Products and Technologies are the crown jewels of the Microsoft platform and being able to jump in and develop on that platform can only enhance your application.

Additional Resources

Throughout this article I've touched upon a number of topics that were not directly related to the task at hand. To save you the time of looking through a search engine for good resources, here are some links that will help you find the information you need: