Delivering Modular SharePoint Workflow Functionality (Part 1 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 1 of 2. (21 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 2: Delivering Modular SharePoint Workflow Functionality (Part 2 of 2)

Contents

  • Overview of Delivering Modular Workflow Functionality

  • Introduction to SharePoint Workflows

  • Exploring the Options: SharePoint Designer and Visual Studio

  • Customizing the Tools

  • Additional Resources

Overview of Delivering Modular Workflow Functionality

A workflow represents a process from start to finish. Whatever the business scenario, the workflow models the events, actions, and situations that make up the process. For an independent software vendor (ISV) that process is often deeply entwined with his or her application. For a customer relationship management (CRM) vendor, that process might be creating a customer record. For a vendor of legal software, the process might be approving a document or tracking matter elements across partners to ensure proper billing. For a financial services software vendor, the process might be the entering and approval of payroll data.

The details of the process do not matter. What does matter is that software applications provide services to their users and, in some cases, provide a significant competitive advantage to allow users to control how those services are consumed. As part of this article, we introduce an example of a vendor who makes software for managing patient information. In that scenario, the vendor's software tracks data, documents, and forms related to a patient through many different treatments and therapies across multiple healthcare providers, and potentially across multiple years of service.

Because of the nature of the vendor's clientele (local government health management offices), there are many legal and practical reasons why the vendor cannot dictate the details of the case management process. For example, in one local office, approval is required before services are authorized for any provider located outside the local office jurisdiction. In another local office, approval might be required only if the provider is located in a different state. In another example, in one local office, case workers might maintain all information in paper files and enter the required details into the case management system only every few days; in another office located in a different city, all case workers might have laptop computers, and all information is maintained electronically and is instantly uploaded to the case management system.

As you can see, there are many different ways to use the same software. At its core, the software tracks patients and their information. In practice, the software's usage is quite different. Unless the maker of the software has the most market share, it would be foolish for that vendor to dictate a universal process. A one-size-fits-all process would need to be so open-ended and flexible that it would be exceedingly difficult to configure and manage for the end user.

Instead, the best approach the vendor can take is to break the processes of the application into components, and allow either the client or the vendor's professional services division to construct a workflow for each specific client from those modular pieces. Using our case-management example, the vendor might identify components such as:

  • Create New Patient

  • Update Patient Contact Information

  • Add Document

  • Add Treatment

  • Add Doctor or Provider

Using the first component (Create New Patient) as an example, the core application defines the information that is required to create a patient record, and what happens inside the application after that patient record is created. The benefit of breaking required functionality into components is that the vendor can still maintain that control over the internal processing of their core application while opening the business process up to whatever each client needs to create a patient record. One client can allow any case manager to create a patient record, while another allows any case manager to enter the information for a new patient, but requires supervisory approval before the patient record is actually created.

The possibilities are endless.

The goal of this article is to examine the options, the process, and the benefits of adding support for SharePoint workflows to your application and breaking that support into components that clients can use to build their own workflows. Much of the functionality to build a workflow is provided by default in Microsoft Office SharePoint Server 2007. What is missing from an ISV’s point of view is direct integration with his or her specific product.

Introduction to SharePoint Workflows

Workflows are a new core functionality provided in Microsoft Office SharePoint Server 2007 and Windows SharePoint Services 3.0 (collectively referred to as Microsoft SharePoint Products and Technologies). Workflows can be used in both Office SharePoint Server and Windows SharePoint Services. These workflows tend to be people-centric, document-centric, or content-centric, which fits in well with the SharePoint mantra of communications and collaboration.

Figure 1. Workflows connect people and content to support a process

Workflows connect people and content

SharePoint workflows typically connect documents or other content to people, and vice versa. Examples of typical SharePoint workflow scenarios include the following:

  • Review

  • Approval

  • Publishing

  • Task Assignments

ISVs might not consider these workflow scenarios to readily accommodate the support of their specific applications. However, these topics are broad, and though Review and Approval might be broader scenarios, any of these workflows can be applied to almost any scenario. Any content that is collected as part of your process can be reviewed and approved before it is actually added to your core application. Any part of your application that needs someone to perform some work can benefit from task assignments.

Extending the core paradigm of SharePoint lists and document libraries, SharePoint workflows are tied to SharePoint items—either items in a SharePoint list or documents in a SharePoint library. In every case, the workflow will act on or process against one of those two items.

After a workflow is associated with a list or library (either directly or by using a content type), it is available for use by all items in that list or library, as shown in Figure 2.

Figure 2. Workflows are available on SharePoint list or document library items

Workflows on SharePoint list items or documents

This article focuses on the very modular components that make up SharePoint workflows. These components are called activities or actions and conditions, depending upon which tool is used to create the workflow. By default, Microsoft SharePoint Products and Technologies provide 32 built-in actions or conditions and 53 built-in activities (some of these are provided by Windows Workflow Foundation). The following list contains examples of these default components:

  • SendEMail

  • CreateTask

  • Assign a To-Do Item

  • UpdateListItem

  • Created by a Specific User

  • Title Field Contains Keywords

These built-in SharePoint activities and actions or conditions are generic, and certainly will not integrate directly with a third-party application. Microsoft leaves this task of creating product-specific components for integration to the ISVs. In this article, we address the process for doing this.

To get started, you must first understand the options and tools that are available to you for creating workflows.

Exploring the Options: SharePoint Designer and Visual Studio

In the world of SharePoint development, a rift exists between experienced developers and power users. Developers tend to shun Microsoft Office SharePoint Designer 2007, the product that replaced Microsoft Office FrontPage. They ignore arguments about Office SharePoint Designer being a new product that improves on Office FrontPage through its improved rendering engine and other elements. Power users, administrators, new developers, and a few enlightened experienced developers see SharePoint Designer for what it is: a tool to use to perform specific tasks.

Whatever your personal feelings about the tool that is SharePoint Designer, as an ISV you cannot ignore it. Your potential clients might be using SharePoint Designer, and it is not going away. You might as well learn to work with it. Ignoring it or not supporting it in the workflow extensions to your product means that you are potentially leaving your product vulnerable to competition or losing sales opportunities. Supporting SharePoint Designer for workflow can expand your potential audience and make your product more competitive. In addition, including support for SharePoint Designer, if you are already planning to support Microsoft Visual Studio, is quite simple. Reaching a wider audience for a low investment seems an easy choice to make.

In this article, we look at both SharePoint Designer and Visual Studio so that you can understand how they work when building workflows, and how to use each of your modular components. Both tool options enable you to create workflows; however, they each have different audiences and capabilities. Remember that it is your customers who will pick a tool to use, not you. Your job is to keep your product’s potential audience as open as possible and that means supporting both tools. Fortunately, doing so is quite easy. Let's look at the tools. We discuss their support in Customizing the Tools.

Visual Studio

The Visual Studio development system provides the full toolkit for workflow developers. If a specific type of functionality is possible to implement in a workflow, you can create it with Visual Studio.

Note

This article assumes you have experience in .NET Framework architecture or development, and are already familiar with Visual Studio.

SharePoint workflow integration is a minor functional addition to Visual Studio. It won’t take you much time to understand the changes.

The primary new functionality in Visual Studio is the addition of the workflow designer, shown in Figure 3.

Figure 3. Two views of the new workflow designer tool in Visual Studio

Views of workflow designer tool in Visual Studio

Visual Studio now also includes special components, described earlier, named activities. Activities are the workflow components that deliver the modular functionality we need. In Figure 3, each of the individual rectangles connected by the arrows represents an activity.

At a very high level, the process of building a workflow in Visual Studio is as follows:

  1. Add activities to the workflow designer palette.

  2. Configure the activities in the Visual Studio Properties window.

  3. Write the code you need to put things together.

That’s it; nothing overly complicated. The problem is always in the implementation details for your specific workflow, which unfortunately are beyond the scope of this article.

As this is an article on modular workflow components, let’s take a closer look at activities. Activities are the building blocks of all Visual Studio workflows; they are the Lego® blocks from which we build our workflow world. Windows Workflow Foundation (WF) provides 31 activities; SharePoint Products and Technologies offer an additional 22. We can build activities to add whatever functionality we require.

Technically, activities are simply .NET Framework controls with some specific functionality. As we'll show, activities are developed as classes and deployed as assemblies. They have properties that can be set, and they can interact with anything—such as components, services, databases—that a .NET Framework component can interact with.

Visual Studio Use By Workflow Builders

If you are building a workflow in Visual Studio, you are a developer. Components you build that target Visual Studio can be used only by people who are familiar with Visual Studio, and with writing code and packaging it for deployment. This gives you a certain amount of freedom because you can reasonably assume that your users understand custom software development. This doesn’t mean that you don’t need to support them, or that you can write insufficiently clear error messages, use weak graphics, or take any other type of development shortcut. It simply means that you are building a component for developers who have an understanding of what is happening behind the scenes.

You might be able to provide more technical information only in error messages rather than in innocuous user-friendly error messages. But you still need to provide a good user experience for your components. To a certain extent, this more development savvy audience might be even more critical of the user experience you provide as they likely have experience with other component suites and so have greater expectations. When we build the example components later in this article, we will keep this in mind and take steps to ensure the user experience is a good one.

SharePoint Designer

SharePoint Designer is a multi-faceted tool that can be beneficial to all users, from power users and developers to interface designers. As with any tool, if SharePoint Designer is used properly, it provides significant value. If not, it can cause problems.

We’ll spend a bit more time walking through SharePoint Designer because it is a new tool and likely less familiar to most architects and developers. Visual Studio, however, while it changed with the addition of the workflow tools, is still Visual Studio; it should not be wholly new to any experienced .NET developer or architect.

From a strictly-workflow point of view, SharePoint Designer provides a basic intuitive interface to build smaller scale, more simplistic workflows. Understand that this is not a slight on the tool; it is a statement of fact. Yes, you can use the tool to build large, complex workflows, but as you’ll see, we don't recommend this.

You build SharePoint Designer workflows by using a wizard-like interface, as shown in Figure 4.

Figure 4. SharePoint Designer workflows based upon a wizard-like interface

SharePoint Designer workflows

SharePoint Designer workflows consist of the following seven constructs:

  • Steps: A group of actions and conditions used to provide structure to the workflow.

  • Conditions: The situations that indicate when to execute a specific Step in a workflow. SharePoint Designer provides nine default conditions, and it is possible to create and add your own.

  • Actions: The functionality that is implemented in a Step. SharePoint Designer provides 23 built-in actions, and again, you can also create your own.

  • Workflow Lookups: Functionality that allows you to retrieve information from other SharePoint items without writing any code.

  • Variables: Standard programming variables that are used to store values. Examples include String and Number.

  • Initiation Forms: Items that allow you to collect information from the user who initiates your workflow.

  • Information Types: Similar in function to Variables, but Variables can also be used to define the interface presented to users to collect the data that is stored. Examples include Multiple Lines of Text and Choice.

To create SharePoint Designer workflows, you need to master these seven constructs. We won't go into details about creating a SharePoint Designer workflow here, as that is not within the scope of this article. However, Figure 4, shown earlier can give you a general understanding of the possibilities and the process.

SharePoint Designer Use By Workflow Builders

Users of components you build with SharePoint Designer are likely to be quite different from users of components you build with Visual Studio. This is due simply to the different audiences for these tools. SharePoint Designer workflow users are typically not highly technical. These builders will anticipate a very user-friendly, wizard-driven interface that can walk them through the process of building and deploying a workflow without requiring that they have much development experience.

However, there are technical limitations to using SharePoint Designer for any enterprise endeavors. Because this article does not address how to decide whether your organization should use SharePoint Designer, we’re not going to discuss the limitations here. We do address using SharePoint Designer because undoubtedly some of your current customers or potential customers will be supporting it.

It is important that you keep this distinction in mind.

Customizing the Tools

With a basic understanding of the workflow landscape, we can now examine how ISVs can provide additional value for their products by extending them through the creation and support of modular components. The ultimate goal of this article is to give you an understanding of the extension possibilities that either Visual Studio or SharePoint Designer provide.

The extension options available to us, as we described earlier, are the following:

  • Visual Studio activities

  • SharePoint Designer actions

  • SharePoint Designer conditions

Although SharePoint Designer supports other constructs (for example, Steps, Lookups, and Variables), they are not available to us to extend or customize. While it might appear that this limits your options, we will show that you will have more than enough opportunity for extension. It might also appear problematic to have three distinct options available to you. But you’ll see that the options are extremely similar and in fact build upon each other.

Creating an Activity

Let's start by creating a custom activity for use with Visual Studio; the SharePoint Designer action we build later will be a relatively simple extension of this activity. The activity we build will allow workflows to update a proprietary software application via a Web service with status information based upon a document review process that varies from one installation of the product to another.

Introducing the Scenario: Contoso Software

Our scenario involves Contoso Software, a small ISV that produces CaseTrak. CaseTrack is case management software designed specifically for local-government mental health offices to manage information about patients diagnosed with mental or behavioral disabilities. We assume that over the last several years, a near-epidemic level increase in the number of autism diagnoses in young children has put a significant strain on the mental health offices, and has resulted in a decrease in their ability to manage cases effectively.

In this scenario, CaseTrak streamlines much of the case management process. As part of their go-to-market strategy, Contoso has done research and determined that a significant number of mental health offices use Windows SharePoint Services 3.0 to manage documents and provide a central location for employees to access information. Contoso has set a strategic direction for their product to integrate with Windows SharePoint Services. Initial efforts have produced Web Parts and a rich Web service API that can provide read/write interaction between CaseTrak and Windows SharePoint Services.

For their next major product release, Contoso is interested in facilitating integration between CaseTrak and Windows SharePoint Services to support the case-review process. The case-review process as it is exposed through the Web Parts currently is entirely manual. Case managers need to remember to initiate the process and track it throughout its lifecycle. This often causes error and missed deadlines.

Contoso has several requests from customers to make this easier. Their initial design called for a single monolithic workflow controlled entirely by CaseTrak. Every client would follow the same core process. Contoso designed several "extensibility points" into the workflow to allow customers some ability to customize the workflow to their specific needs.

Upon review with several key clients, Contoso realizes that this approach will not work for several reasons:

Different regulations apply to the case-review process and even within those laws, there is a lot of opportunity for offices to handle things slightly differently.

Clients using Windows SharePoint Services are accustomed to its flexibility and interface. Forcing them into a different tool just for the case management process is counterproductive.

IT departments at client locations are also resistant to adding another workflow engine to their environment that they must support

Because of these concerns, Contoso’s product architecture group has proposed delivering a modular set of components that either the client or Contoso’s own professional services division can use to build Windows SharePoint Services workflows to the exact specifications of each client and while still taking full advantage of the power of CaseTrak.

The following sections explore how Contoso does that.

Note

Another benefit that Contoso will realize from producing custom activities is the ability to have all of their CaseTrak activities adopt a different look and some additional functionality from the rest of the default activities. Their product marketing department sees a high value in extending their brand into this new area.

Digging into the Scenario

In this scenario, we will be the developer for Contoso Software who is tasked with creating the workflow activity. Before we can really get started writing code, we need to make sure our environment is set up properly. There are numerous resources available on the Web that provide detailed information about setting up a proper SharePoint development environment, so only a simple list of the items you’ll need to have installed follows:

  • Windows Server 2003 or Windows Server 2008

  • Microsoft .NET Framework 3.0

  • Microsoft .NET Framework 2.0

  • Visual Studio 2008

  • Windows SharePoint Server 3.0 or Microsoft Office SharePoint Server 2007

Notice there is no actual requirement for working on a SharePoint platform for this part of our activity because all we are doing here is building a component that operates under WF. As a SharePoint workflow is built upon WF, this means that the component we build will also work in SharePoint Server or Windows SharePoint Server. Item five is on the list just for testing our activity later on.

A WF activity consists of between one and seven individual classes, as follows:

  • ActivityCodeGenerator: Allows you to insert code into the activity during compilation.

  • ActivityDefinition: The primary class of the activity. It controls what the activity actually does and is the only required class. If the other classes are not included in your activity assembly, default values are used.

  • ActivityDesigner: Controls the structural appearance of the activity at design time in Visual Studio for non-theme elements.

  • ActivitySerializer: Provides functionality for custom serialization.

  • ActivityTheme: Controls color and other theme elements of the activity’s appearance in Visual Studio.

  • ActivityToolboxItem: Used in Visual Studio to respond to events triggered when the activity is added to a workflow and also to control the appearance of the activity in the Visual Studio Toolbox.

  • ActivityValidator: Verifies activity properties at both run time and design time.

Because this activity will be part of our packaged product, which clients will see and interact with, we will use most of the classes (specifically, ActivityDefinition, ActivityDesigner, ActivityTheme, ActivityToolboxItem, and ActivityValidator). Remember, though, that the only required class is the default ActivityDefinition class.

Code for the ActivityDefinition Class

The first piece of code to write for our activity is the one required class: ActivityDefinition.

To create the ActivityDefinition class

  1. Start Visual Studio, and then create a Workflow Activity Library project (appropriately found under Workflow in the list of project types).

  2. Name the project CaseTrak.Activities.

  3. After the project opens, rename the default Activity1.cs something more meaningful (for example, StatusUpdater.cs). Visual Studio prompts you to rename all references; click OK.

    We will still need to manually rename one entry in the Activity Designer generated code region within the StatusUpdater.Designer.cs file.

  4. Before we write any code, in Project Properties set your project to be strong named.

  5. Follow your company’s code-signing policies for configuring the proper key.

    Note

    We recommend that your company adopt a code-signing policy if one is not already in place.

  6. Unless you will be using some of the capabilities specific to the .NET Framework 3.5, you should also change the Target Framework to 3.0.

Now you can start adding code.

Code for the Execute Method

The first line of code we are going to add is an override of the Execute method.

protected override ActivityExecutionStatus 
    Execute(ActivityExecutionStatus context)  

This method is required because it is called by the WF host (SharePoint Server or Windows SharePoint Services) when it is time to run the activity.

As you can see in the previous method signature, the Execute method returns a value from the ActivityExecutionStatus enumeration, as follows:

  • Canceling. Activity was ended prematurely

  • Closed. Activity has finished its tasks

  • Compensating. Rolling back changes

  • Executing. Activity is still running

  • Faulting. An error has occurred

  • Initialized. Internal use only; you won't use it in your code

In most cases, the method returns a value of ActivityExecutionStatus.Closed.

The function of this activity is to update CaseTrak with the status of a case review process. CaseTrak supports five statuses for case reviews:

  • Open

  • Closed

  • Pending

  • Deferred

  • In Process

As part of the first wave of Windows SharePoint Services integration, your Web service API exposes a CTSetCaseReviewStatus method, which your activity will call. As you have likely guessed, this method handles the process of changing the status of a Case record. It takes the ID string for the Case record and an integer Status value and returns a Boolean indicator as to whether it succeeded.

public bool CTSetCaseReviewStatus(string sCaseID, int iStatus)

The details of that Web service are not pertinent to this article. In your application’s API, you will have any number of Web services available to perform the required functionality. For the sake of completeness, the Web service project is included with the source code that accompanies this article (see Additional Resources).

The core code for the Execute method for this activity is relatively simple.

try
{
    ct.CTSetCaseReviewStatus(CaseID, Convert.ToInt32(CTStatus));
}
catch (Exception ex)
{
    //See full code listing (https://code.msdn.microsoft.com/modularworkflow) for an error handling event.
}
return ActivityExecutionStatus.Closed;

For now, we leave the catch block empty. The full code listing adds some error handling in the form of an event handler, but we can hold off on that for now. These couple of lines accomplish the work of this activity: Call the Web service (ct is the Web service proxy object, which is declared elsewhere), cast the integer value we received for the iStatus parameter to the appropriate CTStatus enum value, and tell the workflow host (Windows SharePoint Services) that we’re finished.

Code for the Properties

Looking at the previous Web service method call, you can see a reference to two other variables: CaseID and Status. These are simply properties that are a part of our Activity class.

private string _caseID;

[DesignerSerializationVisibilityAttribute 
    (DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("Unique identifier of the current Case")]
        [CategoryAttribute("CaseTrak Configuration")]
        public string CaseID
        {
            get { return _caseID; }
            set { _caseID = value; }
        }

        private CTStatusType _iStatus;

[DesignerSerializationVisibilityAttribute 
    (DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("Status designation of the current Case")]
        [CategoryAttribute("CaseTrak Configuration")]
        public CTStatusType CTStatus
        {
            get { return _iStatus; }
            set { _iStatus = value; }
        }

Notice that the Status property is of type CTStatusType. This is an enumeration of the valid statuses listed earlier.

public enum CTStatusType
{
    Open=1,
    Closed=2,
    Pending=3,
    Deferred=4,
    InProcess=5
}

A benefit of using enumerations for values is that the activity will automatically render this property as a drop-down list of choices in the Visual Studio Properties window (Figure 5).

Figure 5. Custom Properties added to the activity as seen by developers in Visual Studio

Custom properties added to the activity

At this point, we have a functionally complete activity that could be used to build a Visual Studio workflow. It exposes two properties that allow the workflow developer to specify the current Case and also the status to set the Case to. Exactly how those values are arrived at is highly dependent upon the client for which this workflow is being built, and presents a good example of why we are exposing the functionality of our application as modular components.

Remember Visual Studio developers will likely be a more demanding audience. Presumably, they are familiar with other activities for building workflows and will expect that our activity provides at least that much functionality. So let’s circle back and enhance our activity.

Event Handlers

Most of the built-in activities support at least one customizable event, simply named MethodInvoking. As the name implies, this event is raised when the activity is invoked, before it actually does its work. Experienced workflow developers will expect that our activity provides the same functionality.

Event handlers are added as dependency properties.

public static DependencyProperty MethodInvokingEvent = 
    DependencyProperty.Register("MethodInvoking", typeof(EventHandler), 
    typeof(StatusUpdater));

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Description("The MethodInvoking event is raised before CaseTrak is updated with the new status.")]
[Browsable(true)]
public event EventHandler MethodInvoking
{
    add { base.AddHandler(StatusUpdater.MethodInvokingEvent, value); }
    remove { base.RemoveHandler(StatusUpdater.MethodInvokingEvent, value); }
}

With the MethodInvoking dependency property declared and registered, the only thing that remains to do is to raise the method. We do this by adding the following line to the Execute method as the first line, right above the try.

base.RaiseEvent(StatusUpdater.MethodInvokingEvent,this,EventArgs.Empty);

If you look at Figure 5, you’ll see the MethodInvoking property exposed in the Visual Studio Properties window. Whatever method name the developer sets as this property value is now executed before the activity does any other work. The other property shown in Figure 5, CaseTrakError, is another event handler that is included in the full code listing that accompanies this article. It performs a function similar to MethodInvoking except that it allows the workflow developer to specify an event to be called in case an error is returned from CaseTrak.

We now have a functionally complete activity that provides some functionality that workflow developers expect. At this point, we could package and deploy our activity. Instead, we’re going to take a few extra steps to really make our activity shine.

Code for the ActivityValidator

Like most applications, CaseTrak doesn’t respond well to being given information that is not valid or that is incomplete. In fact, it won't work if you attempt to set the status of a case without specifying both the CaseID and the new Status. For this reason, it is important that our activity have functionality that ensures that the workflow developer using it provides a value for the required CaseID and Status properties.

We do this by using the ActivityValidator class.

There is one core method we’ll override in this class, which is appropriately named Validate.

public override ValidationErrorCollection Validate(ValidationManager manager,object obj)
{
     ValidationErrorCollection activityErrors = base.Validate(manager, obj);
     StatusUpdater ctsu = obj as StatusUpdater;
     if ((null != ctsu) && (null != ctsu.Parent))
     {
         if (ctsu.CaseID == null)
         {
              activityErrors.Add(ValidationError.GetNotSetValidationError("CaseID"));
         }
     }
}

Note

In the full source code listing included with this article (Sample Code: Delivering Modular Workflow Functionality in SharePoint Server 2007), you can see how to handle dependency properties, as it is slightly different from the simple properties we address here.

The code for this method is still pretty straightforward. It checks the various properties that must have values to see if they contain them. If they do not, it adds a new validation error to the ValidationErrorCollection object for each property that fails validation and returns that collection. The end result of this process is to display a notification to the workflow developer that indicates things are not quite right, as shown in Figure 6.

Figure 6. Validation errors shown to Visual Studio workflow developers

Validation errors shown to workflow developers

With the ActivityValidator class in place, all we need to do is register it with our ActivityDefinition. We do this by decorating the ActivityDefinition class with an attribute that references our validator class.

[ActivityValidator(typeof(StatusUpdaterActivityValidator))]

Finally, we're ready to enhance the appearance of our activity and make it stand out. We tackle this by using two classes: ActivityDesigner and ActivityDesignerTheme.

Code for the ActivityDesigner and ActivityDesignerTheme

If you're observant, you have already noticed the look of our activity in Figure 6. We show it again in Figure 7, this time with a view of what the activity would like without these customizations. You can decide which one you like better.

Figure 7. Two views of the custom activity: one customized (bottom), one not (top)

Two views of custom activity

Notice the following in the customziations we've made:

  • The customized design is wider to provide room for the CaseTrak logo on the left, and ensures that the name of the activity doesn't wrap onto multiple lines in the workflow designer the way the default activities does. A minor point, perhaps, but it does give our activity a more professional look.

  • The addition of the CaseTrak logo (Figure 7a) helps to tie all of the CaseTrak activities together.

    Figure 7a. CaseTrak logo

    CaseTrack logo

  • The red and black theme of the CaseTrak logo is carried into the activity background.

  • Although less noticeable, we have also changed the placement of the text and image within the activity.

These elements all combine to produce an activity that is more visually appealing than the default activities. Add to this the fact that all CaseTrak activities can easily be made to use this look, and you have something that your product marketing department will take notice of.

Accomplishing this appearance is made quite simple by using two distinct classes. Let's look at the simpler of the two first: ActivityDesignerTheme. This class is primarily responsible for the fonts and colors used to draw the activity. All of the work in this class takes place in the constructor.

public CaseTrakActivityDesignerTheme(WorkflowTheme theme): base(theme)
{
     BackColorStart = Color.Red;
     BackColorEnd = Color.Black;
     BackgroundStyle = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
     ForeColor = Color.White;
}

Easily enough, all we do here is set our foreground and background colors and the background style.

We achieve the rest of our enhanced appearance by using the ActivityDesigner class. This class is responsible for the mechanics of the Activity Design: the size and placement of the rectangles that bound the activity and the text and image contained within the activity.

The first method we look at is OnLayoutSize, which sets the overall dimensions of the activity.

protected override Size OnLayoutSize(ActivityDesignerLayoutEventArgs e)
{
     base.OnLayoutSize(e);
     return new Size(150, 45);
}

As you can see, it simply returns a new Size construct with the dimensions we want; in this case, 150 pixels wide and 45 pixels high (the default activities render at 91 pixels by 43 pixels.)

After the overall size of the activity is set, the rest of the display pieces are controlled by two distinct rectangles within the activity frame: ImageRectangle and TextRectangle.

protected override Rectangle ImageRectangle
{
     get
     {
          Rectangle rectActivity = this.Bounds;
          Size size = new Size(20, 20);
          Rectangle rectImage = new Rectangle();
          rectImage.X = rectActivity.Left + 5;
          rectImage.Y = rectActivity.Top + ((rectActivity.Height - size.Height) / 2);
          rectImage.Width = size.Width;
          rectImage.Height = size.Height;
          return rectImage;
      }
}
protected override Rectangle TextRectangle
{
     get
     {
          Rectangle rectActivity = this.Bounds;
          Size size = new Size(130, 40);
          Rectangle rectText = new Rectangle();
          rectText.X = this.ImageRectangle.Right + 5;
          rectText.Y = rectActivity.Top + 2;
          rectText.Size = size;
          return rectText;
     }
}

Each of these methods merely performs some simple calculations to determine the placement of the Image and Text within the activity.

The final piece of this class is responsible for adding the CaseTrak logo into the activity. We take care of this in the Initialize method of the ActivityDesigner class.

protected override void Initialize(Activity activity)
{
     base.Initialize(activity);
     Bitmap img = CaseTrak.Activities.Properties.Resources.CaseTrakIcon16;
     this.Image = img;

     //Adding our custom Verb.
     verbAbout = new ActivityDesignerVerb(this, DesignerVerbGroup.Edit, 
         "About", new EventHandler(ShowAboutHandler));
     Verbs.Add(verbAbout);
}

The JPEG used for the logo is loaded into the Activity assembly as an embedded resource.

You likely noticed one additional thing in the Initialize method: the ActivityDesignerVerb. Figure 8 shows the end result of these two lines of code.

Figure 8. Adding options to the Activities context menu

Adding options to Activites context menu

The code for the ShowAboutHandler event handler is simple.

private void ShowAboutHandler(object sender, EventArgs e)
{
     MessageBox.Show("CaseTrack(tm) Status Updater Workflow Activity.\r\n\r\n (c) 2007, Contoso Software, Inc.\r\n", "About",MessageBoxButtons.OK, MessageBoxIcon.Information);
}

This is a nice way to add some help text or, as we do here, a copyright message to a custom activity. There are countless other uses for activity verbs, ranging from launching custom wizards to configure activities to interacting directly with your core application to extract test data—whatever your business needs dictate.

With that, all of the code is in place. All we need to do is tie the pieces together. Again, we do that by decorating our classes with attributes.

Add the following attribute to the top of the ActivityDesigner class to tell it which Theme class to use.

[ActivityDesignerTheme(typeof(CaseTrakActivityDesignerTheme))]

Now, add the following attribute to the ActivityDefinition, either before or after the Validator attribute we added previously.

[Designer(typeof(StatusUpdaterDesigner))]

That takes care of jazzing up the look of our activity on the workflow designer palette. We now have just one more place where we can add some pizzazz: the Visual Studio toolbox.

Code for the ToolboxItem Class

As the name implies, the ToolboxItem class is responsible for the presentation and actions of our activity on the Visual Studio Toolbox. Like the DesignerTheme class, the action here takes place in the constructor.

private StatusUpdaterToolboxItem(SerializationInfo info, StreamingContext context)
{
     this.Deserialize(info, context);
     this.Description = "Update Case Status in CaseTrak";
     this.Company = "Contoso Software, Inc.";
     this.DisplayName = "CaseTrakStatus Updater";
     this.Bitmap = new Ã
Bitmap(CaseTrak.Activities.Properties.Resources.CaseTrakIcon16);
}

We need to tie our ToolBoxItem back to our activity and as shown previously, we do this with an attribute added above the ActivityDefinition class declaration.

[ToolboxItem(typeof(StatusUpdaterToolboxItem))]

As you might have guessed, the result of this customization is to add some information about our activity when it is displayed in the Visual Studio Toolbox, as shown in Figure 9.

Figure 9. Adding copyright and other information to the activity for display in the Toolbox

Adding copyright and other information to activity

Note

For this class, the image file loaded into the Toolbox must be 16 pixels by 16 pixels and 256 colors.

Summary for the Activity

We built a custom activity in this section. In the process, we made use of five different classes to make our new activity look good and perform well. We customized everything from the functionality to how the activity looks and acts in Visual Studio. Along the way, we touched upon such diverse capabilities as custom context menus, validation, and generating a common look and feel for all of our company’s activities.

In Delivering Modular SharePoint Workflow Functionality (Part 2 of 2) of this article, we'll take one more step and make all of our hard work available to SharePoint Designer users.

Next step: Delivering Modular SharePoint Workflow Functionality (Part 2 of 2)

Additional Resources

Throughout both parts of this article I touch upon a number of topics that are 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: