Chapter 7. SharePoint Custom Features and Workflows

Features are the backbone of SharePoint development because every custom development project canand really shouldbe deployed as a feature. Features give tremendous control over SharePoint configurations and capabilities at the administrator level. This means that developers can create features and then turn them over to SharePoint administrators without having to get involved repeatedly in small configuration changes.

Workflow is a specialized feature that deserves extra attention because of the power it brings to SharePoint. Workflows allow you to start a series of operations manually or automatically in response to activity in a list. Workflows can be used for simple approval processes or complex system integration. In this chapter I cover all of the different ways to create features from simple hyperlink additions on a page to full-blown customized workflows.

Building Custom Features

In Chapter 3, I introduced the concept of features and defined it as a way to package capabilities that could be deployed to any site. Throughout the book, you have activated various features of SharePoint to enable new functionality within sites. Activating some features, such as publishing, causes major changes in the way that a site behaves. Other features, such as the slide library, add a single atomic piece of functionality. This capability to affect large or small changes makes the idea of creating custom features compelling, and you should think of features as the best practice for deploying any customization from a single web part to complete solutions.

Understanding the Feature Architecture

A SharePoint farm has a straightforward way to keep track of the available features. Each feature on the farm is given a folder in the path C:Program FileCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURES, and the name of the folder is considered to be the name of the feature. The feature folder must contain a file named Feature.xml, which defines the basic information about a feature. Listing 7-1 shows the Feature.xml file for the data connection library feature.

Example 7.1. A Sample Feature.xml File

<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
    Id="00BFEA71-DBD7-4F72-B8CB-DA7AC0440130"
    DefaultResourceFile="core"
    Title="Data Connections Feature"
    Hidden="TRUE"
    SolutionId="7ED6CD55-B479-4EB7-A529-E99A24C10BD3"
    Version="12.0.0.0"
    Scope="Web">
    <ElementManifests>
        <ElementManifest Location="ListTemplatesDataConnectionLibrary.xml" />
    </ElementManifests>
</Feature>

The Feature element is the highest level element in the definition and is required to have a unique identifier and a scope level. The unique identifier is simply a GUID. The scope identifier can be any of the values Farm, WebApplication, Site, or Web and is used to determine the list on which the feature will appear. Table 7-1 lists all of the attributes for the Feature element.

Table 7.1. Feature Element Attributes

Attribute

Required

Value

Description

ActiveOnDefault

No

True or False

Determines whether the feature is active by default when a new web application is created. The default value is True.

AlwaysForceInstall

No

True or False

Determines whether the feature installation process will proceed even if the feature is already installed. I discuss the feature installation process throughout this section. The default value is False.

AutoActivateInCentralAdmin

No

True or False

Determines whether the feature is activated by default in the Central Administration site. The default value is False.

Creator

No

Text

Identifies the feature creator. This element is optional.

DefaultResourceFile

No

Text

Determines the name of a resource file for your feature. The search path for the resource file is C:Program FileCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURES{Feature}Resources.

Description

No

Text

Describes of the feature that will appear on the Feature list in SharePoint.

Hidden

No

True or False

Determines whether the feature is visible on the Feature list. If it is not visible, it can only be activated using the command-line utility STSADM.EXE -o ActivateFeature -name {Feature}. The default value is False.

Id

Yes

Text

Provides a unique identifier for the feature. Typically, this must be a GUID without the curly braces.

ImageUrl

No

Text

Associates an image file with a feature. The image appears in the Feature list alongside the feature description.

ImageUrlAltText

No

Text

Provides alternate text for the feature image.

ReceiverAssembly

No

Strong name

Identifies the strong name of an assembly that will receive activation and deactivation events for the feature. I cover receiver assemblies in the section of this chapter titled "Understanding Feature Receivers."

ReceiverClass

No

Class name

Identifies the class name of the class that will receive activation and deactivation events for the feature.

RequireResources

No

True or False

Indicates whether supporting resources are required for this feature. The default value is False.

Scope

Yes

Farm, WebApplication, Site, Web

Determines on what list the feature appears and therefore at what scope it can be activated.

SolutionId

No

GUID

Specifies the solution to which the feature belongs. I cover creating solutions in Chapter 10.

Title

No

Text

Specifies the title of the feature as it appears in SharePoint.

Version

No

Text

Specifies the version number in n.n.n.n format.

Along with attributes, there are several child elements that can be contained within the Feature element. These include the ActivationDependencies, Properties, and ElementManifests elements. The ActivationDependencies element allows you to establish dependencies between features so that a child feature must be activated before a parent feature. The Properties element allows you to define custom key/value pairs that your feature can access for configuration purposes. The ElementManifests element allows you to specify a file where additional Feature elements are defined.

Of the available elements, you will make the most use of the ElementManifests element. The ElementManifests element contains any number of ElementManifest or ElementFile elements that always have a Location attribute pointing to a manifest file. This manifest file contains information that lets you add new actions to SharePoint menus, add new pages to a site, add new list types to a site, use custom controls, and receive list events. Because the content of the manifest file can vary greatly between features, I'll show you examples of all of these customizations throughout the chapter. Listing 7-2 shows an example manifest file for the data connection library feature.

Example 7.2. Defining a Data Connection Library

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListTemplate
        Name="datasrcs"
        Type="110"
        BaseType="1"
        Hidden="TRUE"
        HiddenList="TRUE"
        OnQuickLaunch="FALSE"
        SecurityBits="11"
        DisplayName="$Resources:core,datasourcesList;"
        Description="$Resources:core,datasourcesList_Desc;"
        Image="/_layouts/images/itdl.gif"
        DocumentTemplate="100"/>
</Elements>

Adding Actions to Menus and Toolbars

Creating a feature that adds an item to a menu or a toolbar can be very simple. In fact, you can create such a feature with no code and two simple XML files. As an example, I'll create a feature that adds an item to the Site Actions menu that lets you access the Apress site in order to download the code for this book. I'll call my new feature CodeDownload. Figure 7-1 shows the activated feature on the Site Actions menu.

Adding an item to the Site Actions menu

Figure 7.1. Adding an item to the Site Actions menu

You begin the creation of a feature by constructing a Feature.xml file. I typically start a new project in Visual Studio just to keep things organized, but it's not a requirement until you build features that include custom assemblies. Once the project is created, I just add an XML file to it named Feature.xml.

Tip

Whenever you are creating feature files, associate the wss.xsd schema file with your XML in order to activate IntelliSense in Visual Studio. You can do this by setting the Schemas property of the XML file to C:Program FileCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEXMLwss.xsd.

As I stated earlier, the only required attributes of the Feature element are Id and Scope. However, you'll typically want a few more attributes such as Title and Description. Additionally, my feature will reference an ElementManifest file named Elements.xml that will contain the definition for my new menu item. For the unique identifier, simply generate a GUID in Visual Studio by selecting Tools

Adding an item to the Site Actions menu

Example 7.3. The Feature.xml File for CodeDownload

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
    Id="BAF0C4A7-C707-47d2-809B-8130BC344048"
    Scope="Site"
    Title="Download Book Code"
    Description="A feature to open the code download page on Apress."
>
  <ElementManifests>
    <ElementManifest Location="Elements.xml" />
  </ElementManifests>
</Feature>

As I noted earlier, the contents of the manifest file vary depending upon the exact functionality you want to create. When adding menu and tool bar items, you'll utilize the CustomAction element in the manifest file. Each CustomAction element defines a single new entry on a menu or a toolbar. Listing 7-4 shows how I use the CustomAction element to define a new item on the Site Actions menu.

Example 7.4. Defining a Custom Action

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id=" 175B270F-239E-4955-97CB-94227E5DAA17"
    GroupId="SiteActions"
    Location="Microsoft.SharePoint.StandardMenu"
    Sequence="1000"
    Title="Download Book Code"
    Description="This action takes you to the Apress code download page."
    ImageUrl="_layouts/images/apress.jpg">
    <UrlAction Url="http://www.apress.com/book/download.html"/>
  </CustomAction>
</Elements>

The CustomAction element has two key attributes, GroupId and Location, that specify the menu or toolbar where a new item will be added. When you set these attributes, you have to use special values that are recognized by the Microsoft.SharePoint.WebControls.FeaturemenuTemplate object, which builds the menu. Admittedly there's a little "magic word" development going on. If you use the values properly, however, you can add an item to menus and toolbars in many useful places. Table 7-2 shows the complete list of possible values for the GroupId and Location attributes.

Table 7.2. Locations and Group IDs

Area

Location

GroupID

Display form toolbar

DisplayFormToolbar

N/A

Edit form toolbar

EditFormToolbar

N/A

New form toolbar

NewFormToolbar

N/A

List view toolbar

ViewToolbar

N/A

List item menu

EditControlBlock

N/A

Library/List New menu

Microsoft.SharePoint.StandardMenu

NewMenu

Library/List Actions menu

Microsoft.SharePoint.StandardMenu

ActionsMenu

Library/List Settings menu

Microsoft.SharePoint.StandardMenu

SettingsMenu

Library Upload menu

Microsoft.SharePoint.StandardMenu

UploadMenu

Site Actions menu

Microsoft.SharePoint.StandardMenu

SiteActions

Site Settings page, Site collection administration links

Microsoft.SharePoint.SiteSettings

SiteCollectionAdmin

Site Settings page, Site administration links

Microsoft.SharePoint.SiteSettings

SiteAdministration

Site Settings page, Galleries links

Microsoft.SharePoint.SiteSettings

Galleries

Site Settings page, Look and feel links

Microsoft.SharePoint.SiteSettings

Customization

Site Settings page, Users and permissions links

Microsoft.SharePoint.SiteSettings

UsersAndPermissions

Site Actions menu for surveys

Microsoft.SharePoint.StandardMenu

ActionsMenuForSurvey

Site Settings page, links for surveys

Microsoft.SharePoint.SiteSettings

SettingsMenuForSurvey

Content type settings links

Microsoft.SharePoint.ContentTypeSettings

N/A

Central Administration Operations page

Microsoft.SharePoint.Administration.Operations

N/A

Central Administration Application Management page

Microsoft.SharePoint.Administration.ApplicationManagement

N/A

Along with the GroupId and Location attributes, I have used the Id, Sequence, Title, Description, and ImageUrl attributes. I have also made use of the UrlAction element. The Id attribute is a GUID that uniquely identifies the new item. The Sequence is a relative value that determines the location of the new item. Setting it to a large value guarantees that it will appear as the last item. The Title is the only required attribute and specifies the text that will appear on the item. The Description is text describing the item, and the ImageUrl is a site-relative path to an image to use on the item. Finally, the UrlAction element specifies the address associated with the new item.

Once the two XML files were completed, I copied them both into a folder under the path C:Program FileCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESCodeDownload. I also copied the graphic for the menu item to C:Program FileCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEImages. Once the files are copied into the appropriate location, you can run the STSADM.EXE utility to install the feature.

STSADM.EXE is located at C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12instsadm.exe. This administrative utility is used for many command-line operations. In fact, you can get a complete listing of the operations just by executing the utility with no arguments. In our case, we want to install the feature that is accomplished with the following arguments:

STSADM.EXE -o installfeature -filename  CodeDownloadfeature.xml -force

Once the new feature is installed, you must reset Internet Information Server to make the feature available. Keep in mind that the feature will not be activated yet; you must go to the features list at the scope level you specified and click the Activate button. In this example, the scope was set to Site level (which actually means site collection). Figure 7-2 shows the new feature available in the site collections Features list.

Activating a custom feature

Figure 7.2. Activating a custom feature

Using Custom Action Pages

In the CodeDownload feature, I simply used a public web site as the target in the UrlAction element. However, you can also use your own ASPX pages as a target. If you do this, you can respond to the user by presenting a custom page in the site, executing code, or both. As an example of this technique, I built a feature called ContentTypeHierarchy that shows a listing of all of content types in a hierarchy. This feature is useful because the standard list of content types doesn't show hidden types or a hierarchical listing. Figure 7-3 shows a partial image of the hierarchical listing.

Showing content types in a hierarchy

Figure 7.3. Showing content types in a hierarchy

For this feature, I built a standard Feature.xml file and manifest file that adds a new link to the Site Settings Galleries section. My new hierarchical view appears directly above the link for the standard view. Listing 7-5 shows the Feature.xml file, and Listing 7-6 shows the manifest file.

Example 7.5. The ContentTypeHierarchy Feature.xml File

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
  Id="2D9921AE-D263-4e2b-B4F7-ABDD6223C8B0"
  Scope="Site"
  Title="Content Type Hierarchy"
  Description="Shows the hierarchical relationship between content types."
  >
 <ElementManifests>
    <ElementManifest Location="Elements.xml" />
 </ElementManifests>
</Feature>

Example 7.6. The ContentTypeHierarchy Manifest File

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="77C52F5D-D5C3-46a1-96EA-2B6B434564C2"
    GroupId="Galleries"
    Location="Microsoft.SharePoint.SiteSettings"
    Sequence="0"
    Title="Site Content Types Hierarchy"
Description="Displays a hierarchy of site content types.">
    <UrlAction Url="_layouts/ContentTypeHierarchy.aspx"/>
  </CustomAction>
</Elements>

The key part of the feature that is new is the reference to the ASP.NET page ContentTypeHierarchy.aspx. This is a custom page that will open when the link is clicked in the Site Settings page. Inside this page, I build the hierarchical list of content types by using the SharePoint object model. I discuss the object model in more detail in Chapter 11, but for now this will help give you a good understanding of how features can interact with SharePoint sites. Listing 7-7 shows the complete code for the ContentTypeHierarchy.aspx page.

Example 7.7. The ContentTypeHierarchy.aspx Page

<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master"%>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
    PublicKeyToken=71e9bce111e9429c"%>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBar"
    src="~/_controltemplates/ToolBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton"
    src="~/_controltemplates/ToolBarButton.ascx" %>

<asp:Content ID="Content2" runat="server"
    ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea">
Content Types Hierarchy
</asp:Content>

<asp:Content ID="Content3" runat="server"
    ContentPlaceHolderID="PlaceHolderPageDescription">
This page shows all Site Content Types and their hierarchical relationships
</asp:Content>

<asp:Content ID="Content4" runat="server" ContentPlaceHolderID="PlaceHolderMain" >

  <TABLE border="0" width="100%" cellspacing="0" cellpadding="0">
  <TR>
    <TD ID="mngfieldToobar">
      <wssuc:ToolBar id="onetidMngFieldTB" runat="server">
      <Template_Buttons>
          <wssuc:ToolBarButton runat="server"
          Text="<%$Resources:wss,multipages_createbutton_text%>"
          id="idAddField"
          ToolTip="<%$Resources:wss,mngctype_create_alt%>"
          NavigateUrl="ctypenew.aspx" ImageUrl="/_layouts/images/newitem.gif"
          AccessKey="C" />
        </Template_Buttons>
</wssuc:ToolBar>
    </TD>
  </TR>
  </TABLE>

<%

    //System Content Type is the root
    SPSite site = SPControl.GetContextSite(Context);
    SPContentTypeCollection types = site.OpenWeb().ContentTypes;
    SPContentTypeId id = types[0].Id;
    Response.Write("<table style='font-size:10pt' border='0'" +
      " cellpadding='2' width='50%'><tr><td>");
    Response.Write("<li><a class='ms-topnav'" +
    " href="/_layouts/ManageContentType.aspx?ctype=" +
    types[0].Id.ToString() + "">" + types[0].Name +
    "</a><span class='ms-webpartpagedescription'>" + types[0].Description +
    "</span></li>");
    ShowChildren(id);
    Response.Write("</ol></td></tr></table>");

%>

</asp:Content>

<script runat="server">

    public void ShowChildren(SPContentTypeId id)
    {
        SPSite site = SPControl.GetContextSite(Context);
        SPContentTypeCollection types = site.RootWeb.ContentTypes;

        Response.Write("<ol>");

        foreach (SPContentType type in types)
        {
            if (type.Parent.Id == id && type.Parent.Id != type.Id)
            {
                Response.Write("<li><a class='ms-topnav'" +
               " href="/_layouts/ManageContentType.aspx?ctype=" +
               type.Id.ToString() + "">" + type.Name +
               "</a><span class='ms-webpartpagedescription'>" +
               type.Description + "</span></li>");
                ShowChildren(type.Id);
            }
        }
Response.Write("</ol>");

    }
</script>

The first thing to note about Listing 7-7 is that it is written in the "old school" ASP style. It uses delimiters to place code directly in the ASPX page. Unfortunately, there is no code-behind model for SharePoint when developing individual pages in this way. Pages in the LAYOUTS directory can have inline code, while pages in the content database cannot have code added to them at all. Generally, I simply create these files directly in Visual Studio and deploy them to the LAYOUTS directory.

The next thing to point out about Listing 7-7 is that it uses placeholders to display content. As I discussed in Chapter 5, the master page provides the content placeholders and the page itself uses them to position content on the page. In this example, I am using the PlaceHolderPageTitleInTitleArea, PlaceHolderPageDescription, and PlaceHolderMain placeholders to display the page title, description, and body, respectively.

Finally, you'll notice that the ASPX page makes use of some user controls written as ASCX files. These controls are standard ones that SharePoint supplies to generate buttons and toolbars. I am using them in the page to recreate the same toolbar that is found in the standard Content Types Gallery. However, you could certainly use your own.

Using Tokens to Retrieve Information

When you write features, you quite often need to know about the site or item that is active when your menu item is clicked. For these scenarios, SharePoint supports a set of tokens you can use in your manifest file to represent sites and items. These tokens allow you to retrieve relevant URLs and item identifiers that can be used within your code. Normally, you would use the tokens as part of a query string (e.g., <UrlAction Url="/layouts/myPage.aspx?Item={ItemUrl}" />) and then extract the value in your ASP.NET code. Table 7-3 lists the available tokens and their functions. Additionally, Chapter 9 includes a complete exercise that uses this technique.

Table 7.3. Manifest Tokens

Token

Description

{ItemId}

ID of a list item that you can use with the SharePoint object model.

{ItemUrl}

URL of the document item being acted upon. Not valid for list items.

{ListId}

GUID of the list where the action occurred.

{RecurrenceId}

Recurrence index of the item.

~site

Site-relative link.

~sitecollection

Site collection-relative link.

{SiteUrl}

URL of the site where the action occurred.

Adding New Files to a Site

Another common scenario where features are useful is when you want to add new files to a site. The files that you add to a site using a feature can take many forms including web pages, master pages, and document templates. Since many of the elements in a SharePoint site are simply files, this opens up a lot of interesting possibilities. In order to add a new file using a feature, you make use of the Module element in combination with the File element through the manifest file. These two elements specify the new files to be added and where they should be added.

The Module element contains a Name attribute for the set of files to be added. The Url attribute designates the address of where the files should be placed. This address can either refer to a library or some other location on the site. The File element also contains a Url attribute that specifies a feature-relative path to the file that will be added. As an example, the following code shows how to add two new master pages to the master page catalog:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MasterPages" List="116" Url="_catalogs/masterpage">
    <File Url="training.master" Type="GhostableInLibrary" />
    <File Url="minimal.master" Type="GhostableInLibrary" />
  </Module>
</Elements>

The target location for the new file is designated by the Url attribute of the Module element. If the target location is a document library, the Type attribute of the File element must be set to GhostableInLibrary. Furthermore the List attribute of the Module element must specify the type of list being targeted. This value is a number that represents the list type. Table 7-4 shows the different values for the List attribute and the corresponding list type.

Table 7.4. List Attribute Values

Value

Description

100

Generic list

101

Document library

102

Survey

103

Links list

104

Announcements list

105

Contacts list

106

Events list

107

Tasks list

108

Discussion board

109

Picture library

110

Data sources

111

Site Template Gallery

113

Web Part Gallery

114

List Template Gallery

115

Form library

116

Publishing library

120

Custom list

200

Meeting Series list

201

Meeting Agenda list

202

Meeting Attendees list

204

Meeting Decisions list

207

Meeting Objectives list

210

Meeting text box

211

Meeting Things to Bring list

212

Meeting Workspace Pages list

300

Portal Sites list

1100

Issue tracking

2002

Personal document library

2003

Private document library

If the target location is not a library, the Type attribute of the File element must be set to Ghostable. This essentially means that you are adding web pages to the site. The File element may further specify a Name attribute for the page when it is added. If a Name attribute is specified, the page will be customized and no longer connected to the original template. If the Name attribute is not specified, the page is uncustomized and will reflect any changes made later to the original file. The following code shows an example of adding new pages where the page templates are in a subfolder named Pages:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Path="Pages" Url="the_internal_use_feature" >
    <File Url="welcome.aspx" Type="Ghostable" />
    <File Url="news.aspx" Type="Ghostable" />
    <File Url="news.aspx" Name="newsletter.aspx" Type="Ghostable" />
  </Module>
</Elements>

Understanding Feature Receivers

In many ways, activating a feature is similar to installing and managing software applications on a Windows desktop. Activating a feature is sort of like an installation process that makes changes to the system, and the Features list in SharePoint is similar to the Add/Remove Programs list in Windows. I point this out because, just like a good installation program, your feature should be mindful of the system changes it makes so that they can be undone during deactivation. While action links are removed automatically during deactivation, many other modifications, such as lists and pages, are not.

Consider the previous example that uses the File element to add new pages to a site during activation. You might initially think that these pages would be removed during deactivation, but they are not. Often the only way for your feature to undo its modifications is to receive notification that the feature is deactivating and respond with some custom code. You can receive feature events by creating a special assembly and associating it with your feature. These assemblies are called feature receivers.

Coding Feature Receivers

As an example of a feature receiver, I'll create a feature that changes the site master page from the default to the minimal master page I presented in Chapter 5. For this feature, I want the site to use the minimal page when the feature is activated, but I want the site to revert to the default master when the feature is deactivated. Therefore, I'll need a way to receive notification when my feature is activating or deactivating.

Creating a class to receive feature events is accomplished by inheriting from Microsoft.SharePoint.SPFeatureReceiver. This class has four methods that you must override: FeatureInstalled, FeatureActivated, FeatureDeactivating, and FeatureUninstalling. The FeatureInstalled method is called after the feature installation is complete. The FeatureActivated method is called after the feature is activated. The FeatureDeactivating method is called before the feature is deactivated, and the FeatureUninstalling method is called before the feature is uninstalled. Using these methods, you can take action at the appropriate time to make changes or roll them back.

In my example, I will use the FeatureActivated method to change the master page to the minimal master and the FeatureDeactivating method to change it back. Once again, I'll use the SharePoint object model to make these changes. Specifically, I will change the MasterUrl and CustomMasterUrl properties of the SPWeb object.

The MasterUrl and CustomMasterUrl are special properties that affect many pages within a SharePoint site. These properties are referenced in site ASPX pages using the special tokens ~masterurl/default.master and ~masterurl/custom.default, which act as placeholders for the properties. You can see these tokens when you look at a site ASPX page, such as Default.aspx, in the SharePoint Designer. Initially, both of these tokens are set to reference the default.master page in the Master Pages catalog, located at _catalogs/masterpage/default.master. However, they can be changed through code in response to an activate or a deactivate event. Listing 7-8 shows how to change the master page to the minimal master page that I describe in Chapter 5.

Note

Along with the dynamic tokens ~masterurl/default.master and ~masterurl/custom.default, SharePoint also supports the static tokens ~site/default.master and ~sitecollection/default.master. These tokens reference the default.master page for the site and site collection, respectively.

Example 7.8. Receiving Feature Events

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Diagnostics;
namespace MinimalMaster
{
  class Receiver:SPFeatureReceiver
  {
    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
      try
      {
        SPWeb site = (SPWeb)properties.Feature.Parent;
        site.MasterUrl = site.ServerRelativeUrl +
        "/_catalogs/masterpage/minimal.master";
        site.CustomMasterUrl = site.ServerRelativeUrl +
        "/_catalogs/masterpage/minimal.master";
        site.Update();
      }
      catch (SPException x)
      {
        logMessage(x.Message, EventLogEntryType.Error);
      }
      catch(Exception x)
      {
          logMessage(x.Message, EventLogEntryType.Error);
      }
  }
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
  {
    try
    {
      SPWeb site = (SPWeb)properties.Feature.Parent;
      site.MasterUrl = site.ServerRelativeUrl +
      "/_catalogs/masterpage/default.master";
      site.CustomMasterUrl = site.ServerRelativeUrl +
      "/_catalogs/masterpage/default.master";
      site.Update();
    }
    catch (SPException x)
    {
      logMessage(x.Message, EventLogEntryType.Error);
    }
    catch (Exception x)
    {
      logMessage(x.Message, EventLogEntryType.Error);
    }
  }

  public override void FeatureInstalled(SPFeatureReceiverProperties properties)
  {
    logMessage("MinimalMaster Feature installed",EventLogEntryType.SuccessAudit);
  }

  public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
  {
    logMessage("MinimalMaster Feature uninstalling", EventLogEntryType.Information);
  }

  private void logMessage(string message,EventLogEntryType type)
  {
    if(!EventLog.SourceExists("SharePoint Features"))
      EventLog.CreateEventSource("SharePoint Features","Application");
    EventLog.WriteEntry("SharePoint Features",message,type);
  }
 }
}

A feature receiver must be installed in the Global Assembly Cache (GAC) in order to work. Therefore, you'll need to provide a strong name for the assembly and install it in the GAC. You can create a new strong name for the assembly directly from the Signing tab of the Properties dialog for your C# project. In this dialog, you can elect to create a new key file or use an existing one.

Tip

During development, I prefer to use the same key file because then the PublicKeyToken doesn't change. It makes it easier to create the Feature.xml file, which must reference the assembly's PublicKeyToken.

Creating the Feature.xml and Manifest Files

Just like any feature you create, you must have an associated Feature.xml file saved in a subdirectory under the FEATURES directory. When creating a feature that will utilize a receiver, you must include the ReceiverAssembly and ReceiverClass attributes of the Feature element. The ReceiverAssembly attribute specifies the complete strong name of the feature receiver. The ReceiverClass attribute specifies the class that inherits from SPFeatureReceiver. Listing 7-9 shows the feature file for my example.

Example 7.9. A Feature File That References a Receiver

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
  Id="142C1ADB-C56E-4fa2-AC5E-C947390BE659"
  Scope="Web"
Title="Minimal Master Page"
  Description="Sets the master page to minimal.master."
  ActivateOnDefault="false"
  AlwaysForceInstall="true"
  Version="1.0.0.0"
  ReceiverAssembly=
  "MinimalMaster, Version=1.0.0.0, Culture=neutral, PublicKeyToken=689f1d0ba493bcce"
  ReceiverClass="MinimalMaster.Receiver"
  >
  <ElementManifests>
    <ElementManifest Location="Elements.xml" />
  </ElementManifests>
</Feature>

The strong name of the feature receiver is a combination of the namespace, version, culture, and PublicKeyToken associated with the assembly. The namespace is designated within the code of Listing 7-8. The version and culture information are typically found in the AssemblyInfo file of the project. However, the culture information is often not supplied so it is simply specified as neutral. The PublicKeyToken is a truncated version of the public key found in the key file. You can obtain the PublicKeyToken for an assembly by using the strong name tool with the following syntax:

sn.exe -T myassembly.dll

For my example to work, I also need to create a manifest file that will load the minimal master page into the Master Page catalog. I presented similar code earlier in the chapter, but I'll include it here so that the example is complete. The following code shows the manifest file for the example:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MinimalMaster" List="116" Url="_catalogs/masterpage">
    <File Url="minimal.master" Type="GhostableInLibrary"/>
  </Module>
</Elements>

Receiving Site, List, and Item Events

In the previous version of SharePoint you could only receive events from document libraries, but this version is capable of handling a wide range of events. Events are handled by creating receiver assemblies that inherit from one of SharePoint's event classes. Once you create an event receiver, it may then be deployed as a feature. Table 7-5 lists the handled events and the associated event class, and when each event is triggered.

Table 7.5. Site, List, and Item Events

Event

Event Class

Description

SiteDeleted

SPWebEventReceiver

Fires after a site collection has been deleted

SiteDeleting

SPWebEventReceiver

Fires just before a site collection is deleted

WebDeleted

SPWebEventReceiver

Fires after a site has been deleted

WebDeleting

SPWebEventReceiver

Fires just before a site has been deleted

WebMoved

SPWebEventReceiver

Fires after a site has been moved

WebMoving

SPWebEventReceiver

Fires just before a site is moved

FieldAdded

SPListEventReceiver

Fires after a new field is added to the list

FieldAdding

SPListEventReceiver

Fires just before a pending new field is added to the list

FieldDeleted

SPListEventReceiver

Fires after a field is deleted from the list

FieldDeleting

SPListEventReceiver

Fires just before a field is deleted from the list

FieldUpdated

SPListEventReceiver

Fires after a field is updated

FieldUpdating

SPListEventReceiver

Fires just before a field is updated

ItemAdded

SPItemEventReceiver

Fires after a new item is added to the list

ItemAdding

SPItemEventReceiver

Fires just before a pending new item is added to the list

ItemDeleted

SPItemEventReceiver

Fires after an item is deleted from the list

ItemDeleting

SPItemEventReceiver

Fires just before an item is deleted from the list

ItemUpdated

SPItemEventReceiver

Fires after a list item is updated

ItemUpdating

SPItemEventReceiver

Fires just before a list item is updated

ItemAttachmentAdded

SPItemEventReceiver

Fires after an attachment is added to a list item

ItemAttachmentAdding

SPItemEventReceiver

Fires just before a pending attachment is added to a list item

ItemAttachmentDeleted

SPItemEventReceiver

Fires after an attachment is deleted from a list item

ItemAttachmentDeleting

SPItemEventReceiver

Fires just before an attachment is deleted from a list item

ItemCheckedIn

SPItemEventReceiver

Fires after an item is checked in

ItemCheckedOut

SPItemEventReceiver

Fires after an item is checked out

ItemCheckingOut

SPItemEventReceiver

Fires just before an item is checked out

ItemUncheckedOut

SPItemEventReceiver

Fires after an item checkout has been canceled

ItemUncheckingOut

SPItemEventReceiver

Fires just before an item checkout has been cancelled

ItemFileConverted

SPItemEventReceiver

Fires after a document conversion has taken place

ItemFileMove

SPItemEventReceiver

Fires after a file has been moved

ItemFileMoving

SPItemEventReceiver

Fires just before a file has been moved

EmailReceived

SPEmailEventReceiver

Fires after an e-mail-enabled list receives a new email

Note

SharePoint still supports the old model of registering event receivers for document libraries through the object model. However, you will not find any page in this version of SharePoint for configuring these event handlers. They can only be configured by using the SharePoint object model.

Coding the Event Receiver

Creating a class to trap one of the events listed in Table 7-5 is fairly straightforward. All you have to do is inherit from the appropriate SharePoint receiver class and code the actions you want to take. As an example, I'll create a receiver that can be used with an Announcements list. My example will add the phrase "For Internal Use Only" to any new announcements added to the list. The idea here is that these are internal team announcements, which are inappropriate to share with customers or partners. Additionally, I'll prevent these items from being deleted once they are added to the list. Listing 7-10 shows the code for implementing the class.

Example 7.10. Trapping List Item Events

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace AnnouncementHandler
{
    public class Processor:SPItemEventReceiver
    {
        public override void ItemAdding(SPItemEventProperties properties)
        {
            properties.AfterProperties["Body"]+= "
 **For internal use only **
";
        }

        public override void ItemDeleting(SPItemEventProperties properties)
        {
            properties.Cancel = true;
            properties.ErrorMessage="Items cannot be deleted from this list.";
        }
    }
}

Each of the events listed in Table 7-5 has an associated Properties object that gets passed in to the event methods. The SPWebEventProperties class contains information about a site before and after an event. The SPListEventProperties class contains information about a list before and after an event. The SPItemEventProperties class contains informa-tion about a list item before and after an event.

In Listing 7-10, you can see that the Body item in the AfterProperties collection of the announcement is altered to append the disclaimer notice. The AfterProperties contain the values that the item will have after the event is complete. You can also access BeforeProperties that have the field values that exist before the event runs. Using the before and after properties to get and set information is used extensively in event programming.

Another common programming task is to stop the deletion of an item. This is accomplished by setting the Cancel property to true. You can also see that in Listing 7-10, I set the ErrorMessage property. The error message will be displayed on a full page if someone tries to delete an item.

Creating the Manifest File

When you create an event receiver, you'll need to create a Feature.xml file in the same way as I have presented throughout this chapter. However, you'll also need to use some new elements in the manifest file. Listing 7-11 shows the manifest file for my example.

Example 7.11. An Event Manifest File

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="104">
    <Receiver>
      <Name>AnnouncementAddHandler</Name>
      <Type>ItemAdding</Type>
      <SequenceNumber>1000</SequenceNumber>
      <Assembly>AnnouncementHandler, Version=1.0.0.0, Culture=neutral,
        PublicKeyToken=689f1d0ba493bcce</Assembly>
      <Class>AnnouncementHandler.Processor</Class>
      </Receiver>
    <Receiver>
      <Name>AnnouncementDeleteHandler</Name>
      <Type>ItemDeleting</Type>
      <SequenceNumber>2000</SequenceNumber>
      <Assembly>AnnouncementHandler, Version=1.0.0.0, Culture=neutral,
      PublicKeyToken=689f1d0ba493bcce</Assembly>
      <Class>AnnouncementHandler.Processor</Class>
    </Receiver>
  </Receivers>
</Elements>

The Receivers element contains a Receiver element for each type of event that you want to receive. The Receivers element has a Name attribute that must be unique for each receiver. The Type attribute designates the kind of event to be received. The SequenceNumber attribute specifies the relative order in which the processing will occur. The Assembly and Class attributes designate the custom assembly and class that will handle the event. Once you have created the Feature.xml file, manifest, and assembly, you can deploy them just like any other feature. Figure 7-4 shows an example of the feature in use.

The "Internal Use" Feature

Figure 7.4. The "Internal Use" Feature

Building and Using Workflows

One of the major new functional additions to SharePoint is its support for workflow. More exactly, SharePoint supports a particular implementation of human workflow. A human workflow is one that requires the interaction of a person to move from step to step. If a document is routed serially to three people, for example, it cannot go to the second person until the first one has completed a review. This stands in contrast to system workflow, which can execute a series of operations without human interaction.

The implementation of human workflow in SharePoint is actually quite simplistic. A SharePoint workflow interacts with humans by assigning tasks and waiting. When the task is marked as completed, the workflow continues to the next step. Therefore, a SharePoint workflow will always have a task list associated with it. Figure 7-5 shows a typical task assignment associated with a workflow.

SharePoint workflows are based on task assignments.

Figure 7.5. SharePoint workflows are based on task assignments.

SharePoint offers two different mechanisms for creating custom workflows. You can use Visual Studio to create sophisticated workflows as features, or you can use the SharePoint Designer to create more simplistic workflows directly in a site. Visual Studio workflows are intended to be reusable features that have all the power of the .NET Framework. SharePoint Designer workflows are really meant for creation by power users for a specific site. Additionally, MOSS comes with some predefined workflows that you can use right away.

Using Built-In Workflows

If you are using MOSS, you will have several built-in workflows available to you out of the box. These workflows, just like the custom one you will create in the exercise at the end of this chapter, are deployed as features that must be activated before they can be used. All of the built-in workflows are activated from the Site Collection Features page. As of this writing, MOSS supports the following workflows out of the box:

  • Collect Signatures: A workflow to gather electronic signatures

  • Disposition Approval: A workflow that is used to determine whether to delete or retain an expired document

  • Collect Feedback: A workflow for collecting feedback from document reviewers

  • Approval: A workflow for obtaining document approval from reviewers

  • Three-State: A workflow for tracking list items

In addition to activating the various workflow features, you must also enable lists to support workflows. Enabling lists to support workflows is accomplished at the farm level. In the Central Administration web site, on the Application Management tab, you'll find a Workflow Settings link. Clicking this link will allow you to configure workflow for the farm. Here you can enable workflow for the farm and set some options for notifying users who have been assigned tasks.

Once workflows are enabled and activated, you can use them with a list. In order to use a workflow with a list, you must create an association between the workflow and the list. You can create an association directly from the list settings or you can associate a workflow with a content type. For example, the Document content type in MOSS already has the Approval, Collect Feedback, and Collect Signatures workflows associated with it. This means that any library using the Document content type will have these workflows available.

Follow these steps to create a workflow association:

  1. Navigate to any document library in VSMOSS.

  2. On the Document Library page, select Settings

    Using Built-In Workflows
  3. On the Customize page, click the link titled Workflow Settings.

  4. On the Add a Workflow page, select the Disposition Approval workflow from the list of available workflows.

  5. Type Retain or Delete in the Name field. This will be the name of the association.

  6. Select New Task List from the Task List section. This will create a new task list for assignments associated with this workflow.

  7. Select New History List from the History List section. This will create a new list used to track workflow milestones for auditing and reporting.

  8. Leave the option checked to allow manual initiation of a workflow, but note that you can automatically start workflows as well.

  9. Click the OK button.

Once the association is made, you can initiate the workflow manually from the drop-down menu attached to any document in the library. Selecting the Workflows menu item brings up a page that lists the associated workflows and lets you choose to initiate one. When you start the workflow, you should see that a new task is created and assigned to a person. If you open the document in Microsoft Word, you should also see that the task appears in the Document Action Panel.

Follow these steps to complete the workflow:

  1. Navigate to the Retain or Delete Tasks list.

  2. Using the drop-down menu associated with the task item, select Edit Item.

  3. Select the option labeled Do Not Delete This Item.

  4. Add some comments in the text area.

  5. Click the OK button.

  6. Navigate back to the document library and note that the workflow is marked as completed.

As a workflow proceeds, different tasks will be assigned to different users depending upon the exact design of the workflow. All of these tasks will collect on the associated task list where they can be reviewed. Additionally, the workflow maintains a history list that allows you to run reports. These reports are created as Excel spreadsheets and can be accessed directly from SharePoint.

Follow these steps to view a history report:

  1. Navigate to the Document Library where you created the Retain or Delete association.

  2. On the Document Library page, select Settings

    Using Built-In Workflows
  3. On the Customize page, click the link titled Workflow Settings.

  4. On the Change Workflow Settings page, click the link titled View Workflow Reports.

  5. On the View Workflow Reports page, click the Activity Duration Report link under the Retain or Delete workflow.

Creating Custom Workflows in Visual Studio

The built-in workflows are useful for many scenarios, but they are relatively unsophisticated. In order to create more complicated workflows, you will have to turn to Visual Studio. Creating workflows in Visual Studio, however, is not a trivial exercise. A custom workflow involves several domains of knowledge, including the .NET Framework 3.0, InfoPath form development, and feature development. A complete detailed discussion of custom workflow would require a separate book, but I provide an overview in this section so that you can begin creating your own. I have also included a complete exercise at the end of the chapter to get you started.

Note

Apress has in fact published a separate book on workflow: Workflow in the 2007 Microsoft Office System by David Mann.

Working in Visual Studio

The basis for all workflow in SharePoint is the Windows Workflow Foundation (WF). WF is a workflow engine and set of programming classes for building workflows (both human and system) on the Windows platform. It is important to understand that WF is not a part of SharePoint. WF is actually part of the operating system and is installed as a component of the .NET Framework 3.0. WF has an in-process engine that loads activities for execution. Activities represent the steps in the workflow to be executed. When you create a workflow, you actually create the activities to be executed.

WF provides a programming namespace System.Workflow with a set of classes that can be used in Visual Studio 2005 for creating workflows. In theory, you can create a custom workflow by simply building an assembly that utilizes the classes in this namespace. However, you would never want to actually create a workflow in this manner. Instead, Microsoft has provided several additional layers of abstraction that make developing workflows easier.

The first layer of abstraction is the Microsoft Visual Studio 2005 Extensions for Windows Workflow Foundation. The extensions are required for designing workflows in Visual Studio 2005. You can download the extensions from the Microsoft site at http://www.microsoft.com/downloads/details.aspx?FamilyId=5D61409E-1FA3-48CF-8023-E8F38E709BA6&displaylang=en. When you install them, you will see that new workflow projects have been added to Visual Studio as shown in Figure 7-6.

Even though you have added the workflow extensions to Visual Studio and could certainly start a new project, you'll also want to install the SharePoint template projects before getting started. The template workflow projects are part of the Enterprise Content Management Starter Kit, which also provides white papers and tools specifically targeting SharePoint workflow development. The starter kit installs as part of the MOSS SDK and it gives you specific projects for SharePoint workflows as shown in Figure 7-7. Once these templates are installed, you are ready to start developing workflows in Visual Studio 2005.

Tip

If you install the Enterprise Content Management Starter Kit, but do not see the appropriate project templates in Visual Studio, try executing devenv.exe /setup from the command line. This command will force Visual Studio to rebuild the project template listings.

Adding workflow project types to Visual Studio 2005

Figure 7.6. Adding workflow project types to Visual Studio 2005

Adding SharePoint workflow templates to Visual Studio 2005

Figure 7.7. Adding SharePoint workflow templates to Visual Studio 2005

A workflow is a set of activities put together to represent a process. The activities are atomic units of work that execute in an order designed to model the real steps that people in an organization might execute. As the designer of a workflow, you can utilize predefined activities or create your own custom activities. First, I'll show you how to use the predefined activities, and later I'll show you how to build a custom activity.

When you create a new workflow project in Visual Studio, you may select from the sequential workflow template or the state machine workflow template. The sequential workflow is a series of steps that occur in serial or parallel. The state machine workflow determines the next step in a workflow based on the current state. Creating a workflow project will give you a new class that inherits from either System.Workflow.Activities.SequentialWorkflowActivity or System.Workflow.Activities.StateMachineWorkflowActivity, depending upon the project template you use.

Workflow classes in Visual Studio have a designer associated with them to help you create the workflow. The designer allows you to drag and drop activity shapes from the toolbox that represent sequential steps or machine states. Then you can write code to define the actions that should occur for a sequential step or when the machine reaches a certain state. There are a tremendous number of activity controls available in the toolbox to support workflow including loops, branches, and states. Each one of these controls requires some level of configuration, and many require associated code to function correctly. Figure 7-8 shows the toolbox and designer in a typical workflow project and Table 7-6 describes the available activities.

Using the Workflow Designer

Figure 7.8. Using the Workflow Designer

Table 7.6. Workflow Activities

Activity

Description

Code

An activity that executes custom code

Compensate

An activity used within an exception-handling activity

ConditionedActivity Group

A set of activities executed under certain conditions

Delay

An activity that waits for a time period

EventDriven

An activity used to specify an event that should be listened for

ExceptionHandler

An activity that contains error-handling operations

IfElse

An activity containing other activities that will execute if a condition is True

InvokeWebService

An activity that calls a web service

InvokeWorkflow

An activity that starts another workflow

Listen

An activity that waits for an event to occur

Parallel

An activity that contains other activities that will execute in parallel

Replicator

An activity that executes multiple instances of contained activities

SelectData

An activity that receives a data class from the host process

Sequence

An activity that contains other activities that will execute in sequence

SetState

An activity that changes the state of a workflow

State

An activity that defines workflow state

StateInitialization

An activity that occurs when a state is initialized in the workflow

Suspend

An activity that pauses the workflow

Terminate

An activity that ends the workflow

Throw

An activity that throws an exception

TransactionalContext

An activity that contains other activities that are grouped into a transaction

UpdateData

An activity that sends a data class to the host process

WaitForData

An activity that waits for a data class from the host

WaitForQuery

An activity that receives a request for a data class

WebServiceReceive

An activity that waits for a web service call

WebServiceResponse

An activity that responds to a web service

While

An activity containing other activities that will execute while a condition is True

Configuring activities in the Workflow Designer generally involves setting property values that fall into one of two major categories: correlation tokens or members. Correlation tokens are placeholders you create that will be filled in by the workflow engine when an instance of your workflow starts. These tokens can have any name you want, but certain activities must share the same token in order for the workflow engine to track and manage the workflow instances. For example, controls that all deal with the life of a task item will share a single correlation token to indicate that they are all associated with the same task item.

Members are either variables or property code structures that maintain the state of the workflow. For example, if you are assigning a new task in a workflow, the CreateTask activity shown in Figure 7-8 will require you to define a member for tracking the unique identifier of the created task. You may also have members to track the values of the fields associated with the tasks. The Workflow Designer provides an interface for defining and tracking members and will automatically generate the required code in your project. You'll get a chance to use these interfaces in the exercise at the end of the chapter.

Once you have configured the activities, you can write code to run when each step of the process is executed. Each activity has methods associated with it that can be automatically generated in your project. In the designer, you can right-click any control and select Generate Handlers from the context menu. This will stub out the available methods for the activities in your code.

Creating InfoPath Forms

Along with creating the workflow, you must also create a series of InfoPath forms or ASPX files in order to use the workflow in SharePoint. The forms or pages are used to provide an interface to initiate a workflow or complete an assigned task. In this chapter, you'll make use of InfoPath forms for this purpose. These InfoPath forms must be fully trusted, compliant with InfoPath Forms Services, and contain fields for use inside of the workflow you create in Visual Studio. You have already seen examples of these forms when you used the built-in MOSS workflows. Figure 7-9 shows an example of such a form seen when the Approval workflow is started.

InfoPath forms are used to gather workflow data.

Figure 7.9. InfoPath forms are used to gather workflow data.

Deploying Workflows

After the workflow project is created in Visual Studio and the required InfoPath forms are built, you may deploy the workflow to SharePoint. As I mentioned previously, workflows are deployed as features that must be activated before they can be used. Therefore, you'll need to create a Feature.xml file and a manifest file. The feature must also deploy the InfoPath forms, and the workflow assembly must have a strong name and be installed in the GAC.

The Feature.xml file references the workflow engine by designating the assembly Microsoft.Office.Workflow.Feature as the ReceiverAssembly. The manifest file subsequently references your custom assembly and InfoPath forms. At run time, the WF engine loads your assembly and feeds the data from the InfoPath forms into the workflow. Again, the best way to get familiar with this entire process is to work the exercise at the end of this chapter.

Creating Workflows in SharePoint Designer

Unlike the workflows created in Visual Studio, SharePoint Designer workflows are not deployed as features and cannot be used across multiple lists or with content types. When you use the SharePoint Designer to create workflows, you must bind the workflow directly to a specific list. The SharePoint Designer provides a limited set of activities that can be used safely by a power user to create a workflow. The workflow steps are saved directly to the associated site and are compiled by SharePoint when it is run. As a developer, you can also create custom activities and make them available in the SharePoint Designer. I cover creating custom activities in the section of this chapter titled "Creating Custom Activities."

To begin creating a workflow with the SharePoint Designer, you must first open a site. Once the site is open, you may select New

Creating Workflows in SharePoint Designer

When you create a new workflow, you can also specify initiation parameters and variables. Initiation parameters will be provided by the user when the workflow starts. If the workflow is started automatically, you can specify default values that will be used instead. Variables are used by the workflow to track values between steps. This is just a simple state management system so that one step can set a value that is retrieved by another. The Initiation and Variables buttons shown in Figure 7-10 open dialogs for adding initiation parameters or variables, respectively.

The heart of the Workflow Designer is the step creation interface. Using this interface, you can design a series of steps for the workflow. Each step consists of a condition and an action. If the condition evaluates as True, the action is executed. You may have as many steps containing as many condition/action sets as you need to define the workflow. Figure 7-11 shows a simple step checking to see whether the request is out of date.

Once you have completed designing the workflow, you can check it for errors by clicking the Check Workflow button. If your workflow has no errors, you simply save it and it is ready to use. You can initiate the workflow directly from the list in the same way as you did for any other workflow. If you have defined initiation parameters, the user will be prompted to enter them before the workflow starts.

Binding a workflow to a list

Figure 7.10. Binding a workflow to a list

A workflow step

Figure 7.11. A workflow step

Creating Custom Activities

Throughout this chapter, I have shown you how to use the predefined activities in Visual Studio and the SharePoint Designer to create and deploy workflows. While there are many predefined activities to use in your workflows, you will undoubtedly want to create your own custom activities to handle special situations. This will be particularly true if you are supporting power users who are creating workflows in the SharePoint Designer because they cannot write any custom code in their workflows. In this section, I briefly walk you through the process of creating a custom activity. Again, this topic is a large one and a detailed examination is beyond the scope of this book. However, I'll present the basics here to get you started.

Coding a Custom Activity

A custom activity is created in Visual Studio by inheriting from the base activity class System.Workflow.ComponentModel.Activity or any predefined activity. As an example, I have created an activity that simply writes a message to the event log. The complete code for my activity is shown in Listing 7-12.

Example 7.12. The LogActivity Class

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;

namespace CustomActivities
{
  [Designer(typeof(ActivityDesigner), typeof(IDesigner)),
  ToolboxItem(typeof(ActivityToolboxItem)),
  Description("Logging Activity"),
  ActivityValidator(typeof(LogActivityValidator))]
  public sealed class LogActivity : Activity
  {
    //Name of the Log
    public static DependencyProperty LogNameProperty =
    DependencyProperty.Register("LogName", typeof(string), typeof(LogActivity));

    public string LogName
    {
      get { return ((string)(base.GetValue(LogActivity.LogNameProperty)));}
      set { base.SetValue(LogActivity.LogNameProperty, value); }
    }

    //Message Property
    public static DependencyProperty MessageProperty =
    DependencyProperty.Register("Message", typeof(string), typeof(LogActivity));

    public string Message
    {
      get { return ((string)(base.GetValue(LogActivity.MessageProperty)));}
      set { base.SetValue(LogActivity.MessageProperty, value);}
    }
//Entry Type
    public static DependencyProperty EntryTypeProperty =
   DependencyProperty.Register("EntryType", typeof(string),typeof(LogActivity));

    public string EntryType
    {
      get { return ((string)(base.GetValue(LogActivity.EntryTypeProperty))); }
      set { base.SetValue(LogActivity.EntryTypeProperty, value);}
    }

    protected override ActivityExecutionStatus
      Execute(ActivityExecutionContext executionContext)
    {
      if (!EventLog.SourceExists(LogName))
        EventLog.CreateEventSource(LogName, "Application");

      switch (EntryType)
      {
        case "Error":
          EventLog.WriteEntry(LogName, Message, EventLogEntryType.Error);
          break;
        case "Failure":
          EventLog.WriteEntry(LogName, Message, EventLogEntryType.FailureAudit);
          break;
        case "Information":
          EventLog.WriteEntry(LogName, Message, EventLogEntryType.Information);
          break;
        case "Success":
          EventLog.WriteEntry(LogName, Message, EventLogEntryType.SuccessAudit);
          break;
        case "Warning":
          EventLog.WriteEntry(LogName, Message, EventLogEntryType.Warning);
          break;
      }

      return ActivityExecutionStatus.Closed;
    }
  }

  public class LogActivityValidator : ActivityValidator
  {
    public override ValidationErrorCollection
      ValidateProperties(ValidationManager manager, object obj)
    {
      ValidationErrorCollection errors = new ValidationErrorCollection();
      LogActivity activity = obj as LogActivity;
if (activity == null)
        errors.Add(new ValidationError("Not a valid activity.", 1));
      else
      {
        if (activity.LogName == null)
          errors.Add(new ValidationError("Not a valid log name.", 2));
        if (activity.Message == null)
          errors.Add(new ValidationError("Not a valid message.", 3));
        if (activity.EntryType == null)
           errors.Add(new ValidationError("Not a valid entry type.", 4));
      }
      return errors;
    }
  }
}

When you define the new activity class, you must add attributes to the class that specify the appearance of the activity in the Visual Studio environment and the name of the class that will handle validation for the activity. In my example, I use the System.Workflow.ComponentModel.Design.ActivityDesigner class to define the appearance of my activity, and the custom class LogActivityValidator for validation. The ActivityDesigner class will give my activity the default appearance and behavior of a standard activity in the Visual Studio environment. My custom LogActivityValidator class inherits from System.Workflow.ComponentModel.Compiler.ActivityValidator, which allows me to override the ValidateProperties method and check the values of the activity properties.

I designed my activity to support three properties: LogName, Message, and EntryType. These three properties correspond to the three arguments required to make an entry using the WriteEntry method of the System.Diagnostics.EventLog class. Creating properties for an activity is done by coding standard property structures for access methods. However, the member variable that stores the property value is coded as a System.Workflow.ComponentModel.DependencyProperty type. Coding the member variables in this way exposes it to the Visual Studio and SharePoint Designer environments so that people who use the activity can configure it at design time just like any other activity. Additionally, you use the SetValue and GetValue methods of the base Activity class to manage the member variable value instead of setting it directly.

Once you have the class created, you must give it a strong name and install it in the GAC. At this point, it is ready for use. In Visual Studio, you may simply right-click the toolbox inside of a workflow project and select Choose Items from the context menu. This will allow you to select the new activity and add it to the toolbox. Figure 7-12 shows my custom activity in a workflow project.

The LogActivity in Visual Studio

Figure 7.12. The LogActivity in Visual Studio

Using Custom Activities in the SharePoint Designer

If you wish to use the new activity in the SharePoint Designer, you'll also have to create a file with an .ACTIONS extension that describes the activity and how to represent it in the Workflow Designer. This is because the Workflow Designer does not use property sheets like Visual Studio. As you have seen already, it uses sentences and hyperlinks for configuration. Listing 7-13 shows the complete CUSTOM.ACTIONS file for my activity.

Note

Be sure to change the strong name in Listing 7-13 to match your assembly.

Example 7.13. The CUSTOM.ACTIONS File

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
  <Actions Sequential="then" Parallel="and">
    <Action Name="Log an Event" ClassName="CustomActivities.LogActivity"
     Assembly="CustomActivities, Version=1.0.0.0, Culture=neutral,
     PublicKeyToken=689f1d0ba493bcce" AppliesTo="all" Category="Logging">
<Parameters>
        <Parameter Name="Message" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="LogName" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="EntryType" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
      <RuleDesigner Sentence="Log %1 in %2 as %3">
        <FieldBind  Id="1" Field="Message"
         DesignerType="TextArea" Text="this message"/>
        <FieldBind  Id="2" Field="LogName"
        DesignerType="TextArea" Text="this log"/>
        <FieldBind  Id="3" Field="EntryType"
        DesignerType="DropDown" Text="this event type">
          <Option Name="Information" Value="Information"/>
          <Option Name="Success" Value="Success"/>
          <Option Name="Warning" Value="Warning"/>
          <Option Name="Error" Value="Error"/>
          <Option Name="Failure" Value="Failure"/>
        </FieldBind>
      </RuleDesigner>
    </Action>
  </Actions>
</WorkflowInfo>

The Actions element is simply a container for Action elements that define the activity and its appearance. The Action element defines the assembly that contains the custom activity, the list types that can use it, and the category it will appear under in the Workflow Designer.

The Parameters element contains a Parameter element for each of the properties defined in the custom activity. The Parameter element defines the data type of the property and whether it is an input or an output value. In this case, all three parameters are strings that are input during the workflow design process.

The RuleDesigner element contains multiple FieldBind elements that map property values in the activity to Parameter elements. The FieldBind elements also define the DesignerType that determines the type of user interface associated with each parameter. In my example, the Message and LogName properties will display a text box while the EntryType property will display a pick list.

The Sentence attribute defines the sentence that will appear in the Workflow Designer and has placeholders for the property values. In my case %1 will map to the Message, %2 will map to the LogName, and %3 will map to the EntryType.

Once you have completed the ACTIONS file, it needs to be saved into the directory C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATE1033Workflow alongside the WSS.ACTIONS file that defines the basic activities for the Workflow Designer. Once the file is copied, you must reset Internet Information Server to see the changes. Then you can start the SharePoint Designer and create a new workflow. The new activity will be available under the category you defined in the ACTIONS file. In my example, the new activity is located under the Logging category. Figure 7-13 shows my new activity in the Workflow Designer.

The LogActivity in the Workflow Designer

Figure 7.13. The LogActivity in the Workflow Designer

Considering Workflow Options

Now that I have reviewed the various options for creating workflows, it's appropriate to put them into perspective. It's important to understand exactly what WF brings to SharePoint and where it falls short. Planning for workflows should be done carefully and with consideration for the impact the various choices will have on the organization.

The built-in workflows that ship with MOSS are rather simplistic implementations that allow serial/parallel approval. The user who is starting the workflow can specify the reviewers and initiate the process. MOSS will assign tasks to each reviewer and track the progress of the workflow.

The good thing about MOSS workflows is that they are simple to use and available right away. For many organizations, these workflows can act as a good replacement for the normal process of attaching a document to an e-mail and sending it out to many people. In fact, this is a great place to start, because the people in any organization are going to take a long time to adapt to the use of workflow for daily tasks. These simple workflows will become important change catalysts over the first year of SharePoint adoption.

As workflow adoption progresses, it is likely that someone at the departmental level will get interested and want to do more. A nontechnical power user will realize what can be done with the SharePoint Designer, and they will dive in. This is certainly true for FrontPage when it is used in conjunction with SharePoint Portal Server, and I expect to see the same thing happen with the SharePoint Designer when used in conjunction with this version of SharePoint.

The good and bad things about SharePoint Designer workflows are the same as the good and bad things about the SharePoint Designer itself. Power users can initially do a lot without involving IT, and this will seem like a good thing. Just like FrontPage before it, however, this can become a nightmare. As power users get in trouble with the SharePoint Designer, they will increasingly rely on technical people to help them out. Then when IT does get involved, they'll often discover that the power users have not organized their projects well, have utilized nonstandard approaches, and have generally created a rat's nest.

I am certainly seeing a lot of excitement among developers with regard to developing workflows in Visual Studio. For many, this represents a brand-new domain of knowledge that will be fun to master and provide a much needed break from creating yet another ASP.NET database application. However, developing workflows for SharePoint in Visual Studio is not easy. In order to make it happen, you need strong skills in InfoPath, SharePoint feature development, SharePoint object model programming, the Visual Studio Workflow control set, and C#. The current process for creating SharePoint workflows is complicated and requires many seemingly arbitrary configuration steps.

The good thing about Visual Studio workflow development is that it gives you complete control over the solution. The bad thing is that it will take a highly skilled developer to pull it off. It will be difficult to design workflows so that they are fully configurable by the end user.

Another consideration is the use of third-party engines. At this point, all of the major vendors have announced that their next versions will be based on WF and fully support SharePoint 2007. Without a doubt, the design environments, deployment tools, and reporting capabilities will be vastly superior to what can be achieved in Visual Studio alone. Therefore, an assessment of these tools is critical for any organization that requires workflow beyond what the SharePoint Designer can provide.

Note

My favorite workflow engine for SharePoint remains K2.net. This is a highly functional engine that has significant capability and is much easier to use than the Visual Studio templates. You can find out more at http://www.k2workflow.com.

Exercise 7.1. Building an Employee Performance Review Workflow

In this exercise, you will create a complete human workflow for automating the employee performance review process. You will create InfoPath forms, a workflow assembly, and a feature. This workflow could be used with an associated InfoPath form, Word document, or even a list to assign and track the steps necessary to complete the review process. In this exercise, you'll create a Contact list of employees and use the workflow to schedule their performance reviews.

Creating the InfoPath Workflow Forms

Before you can start creating the workflow in Visual Studio, you need to create the InfoPath forms that will support the workflow in SharePoint. A SharePoint workflow requires three separate InfoPath forms: the association form, the initiation form, and the action form. The association form is displayed to the user when the workflow is first associated with a list. The initiation form is displayed to the user when the workflow is launched. The action form is displayed when a user completes an assigned task. Over the next few sections, you will create all three of these forms.

Creating the Workflow Association Form

The association form allows users to specify key information when a workflow is first associated with a list. In this exercise, you will specify information about the manager who will be performing the performance reviews. The assumption here is that you will create separate lists for each manager that will use the workflow.

Follow these steps to create the association form:

  1. Start Microsoft InfoPath.

  2. In the Getting Started dialog, click the link titled Design a Form Template.

  3. In the Design a Form Template dialog, select a Blank template and click the OK button to start with a blank form.

  4. When the blank form opens, click the Data Source link in the Design Tasks pane.

  5. Right-click the myFields node and select Properties from the context menu.

  6. Rename the node flowFields in the Name text box and click the OK button.

  7. Right-click the flowFields node and select Add.

  8. In the Add Field or Group dialog, type managerUsername in the Name field and click the OK button.

  9. Right-click the flowFields node and select Add.

  10. In the Add Field or Group dialog, type managerFullname in the Name field and click the OK button.

  11. Right-click the flowFields node and select Add.

  12. In the Add Field or Group dialog, type reviewType in the Name field and click the OK button.

  13. Right-click the flowFields node and select Add.

  14. In the Add Field or Group dialog, type reviewComments in the Name field and click the OK button.

  15. Click on the Design Tasks link at the top of the Data Source pane.

  16. Click the Layout link in the Design Tasks pane.

  17. Drag the Custom Table layout onto the blank form.

  18. In the Insert table dialog, enter 1 in the Columns field and 3 in the Rows field, and click the OK button.

  19. Click the Design Tasks link at the top of the Layout pane.

  20. Click the Controls link in the Design Tasks pane.

  21. Uncheck the box labeled Automatically Create Data Source.

  22. Drag a text box from the Controls pane and drop it in the top cell of the table.

  23. In the Text Box Binding dialog, select the managerUsername field and click the OK button.

  24. Drag a text box from the Controls pane and drop it in the middle cell of the table.

  25. In the Text Box Binding dialog, select the managerFullname field and click the OK button.

  26. Drag a button from the Controls pane and drop it in the bottom cell of the table.

  27. Right-click the button and select Button Properties from the context menu.

  28. Enter Done in the Label field.

  29. Click the Rules button.

  30. In the Rules dialog, click the Add button.

  31. In the Rules dialog, click the Add Action button.

  32. In the Action dialog, select Submit Using a Data Connection from the drop-down list.

  33. Click the Add button.

  34. In the data connection wizard, select to Create a New Connection to Submit Data and click the Next button.

  35. On the next screen select the option to submit the data to The Hosting Environment. This will ensure that the contents of the form are submitted back to the SharePoint workflow process.

  36. Click the Next button.

  37. On the next screen, name the connection Association and click the Finish button.

  38. In the Action dialog, click the OK button.

  39. In the Rules dialog, click the Add Action button.

  40. Select Close the Form from the drop-down list.

  41. Uncheck the box labeled If Changes Have Not Been Saved, Prompt the User to Save.

  42. Click the OK button.

  43. In the Rule dialog, click the OK button.

  44. In the Rules dialog, click the OK button.

  45. In the Button Properties dialog, click the OK button.

  46. Save the form to an appropriate place for safekeeping such as AssocForm.xsn. You'll move the form later, so you just need a temporary location for now. Figure 7-14 shows the completed form.

The workflow association form

Figure 7.14. The workflow association form

Creating the Workflow Initiation Form

The initiation form allows users to specify key information when a workflow instance is first started. In this exercise, you will specify information about the type of performance review and add any relevant comments. Because the initiation form uses the same data source as the association form, you'll create the initiation form by modifying the association form.

Follow these steps to create the initiation form:

  1. Open AssocForm.xsn in Design mode in InfoPath 2007 if it is not already open.

  2. Select File

    The workflow association form
  3. In the Save As dialog, change the form name to InitForm.xsn and click the Save button. This ensures that the data elements and namespace for the initiation form exactly match those of the association form. This allows the forms to share data values.

  4. Remove the text box controls for the managerUsername and managerFullname fields from the form. Do not delete the button.

  5. Click the Design Tasks link at the top of the task pane.

  6. Click the Controls link in the Design Tasks pane.

  7. Uncheck the box labeled Automatically Create Data Source.

  8. Drag a text box from the Controls pane and drop it in the top cell of the table.

  9. In the Text Box Binding dialog, select the reviewType field and click the OK button.

  10. Drag a text box from the Controls pane and drop it in the middle cell of the table.

  11. In the Text Box Binding dialog, select the reviewComments field and click the OK button.

  12. Save the initiation form. Figure 7-15 shows the final initiation form.

The workflow initiation form

Figure 7.15. The workflow initiation form

Creating the Workflow Action Form

The action form is presented to the user when he or she has a task to perform in the workflow. In this exercise, the form is simply used to record the completion of the task. In more complicated workflows, the form could support different views for different user roles along with supporting fields.

Follow these steps to create the action form:

  1. Start Microsoft InfoPath.

  2. In the Getting Started dialog, click the link titled Design a Form Template.

  3. In the Design a Form Template dialog, select a Blank template and click the OK button.

  4. When the blank form opens, click the Data Source link in the Design Tasks pane.

  5. Right-click the myFields node and select Properties from the context menu.

  6. Rename the node actionFields in the Name text box and click the OK button.

  7. Right-click the actionFields node and select Add.

  8. In the Add Field or Group dialog, type reviewCompleted in the Name field.

  9. Select True/False (Boolean) from the Data Type drop-down list and click the OK button.

  10. Right-click the actionFields node and select Add.

  11. In the Add Field or Group dialog, type reviewNotes in the Name field and click the OK button.

  12. Click the Design Tasks link at the top of the Data Source pane.

  13. Click the Layout link in the Design Tasks pane.

  14. Drag the Custom Table layout onto the blank form.

  15. In the Insert Table dialog, enter 1 in the Columns field and 3 in the Rows field, and click the OK button.

  16. Click the Design Tasks link at the top of the Layout pane.

  17. Click the Controls link in the Design Tasks pane.

  18. Uncheck the box labeled Automatically Create Data Source.

  19. Drag a check box from the Controls pane and drop it in the top cell of the table.

  20. In the Check Box Binding dialog, select the reviewCompleted field and click the OK button.

  21. Drag a text box from the Controls pane and drop it in the bottom cell of the table.

  22. In the Text Box Binding dialog, select the reviewNotes field and click the OK button.

  23. Drag a button from the Controls pane and drop it in the bottom cell.

  24. Right-click the button and select Button Properties from the context menu.

  25. Enter Done in the Label field.

  26. Click the Rules button.

  27. In the Rules dialog, click the Add button.

  28. In the Rules dialog, click the Add Action button.

  29. In the Action dialog, select Submit Using a Data Connection from the drop-down list.

  30. Click the Add button.

  31. In the data connection wizard, select to create a new data connection to submit data and click the Next button.

  32. On the next screen, select the option to submit the data To the Hosting Environment so the form will be submitted back to the SharePoint workflow process.

  33. Click the Next button.

  34. Name the connection Action and click the Finish button.

  35. In the Action dialog, click the OK button.

  36. In the Rules dialog, click the Add Action button.

  37. Select Close the Form from the drop-down list.

  38. Uncheck the box labeled If Changes Have Not Been Saved, Prompt the User to Save.

  39. Click the OK button.

  40. In the Rule dialog, click the OK button.

  41. In the Rules dialog, click the OK button.

  42. In the Button Properties dialog, click the OK button.

  43. Save the form to an appropriate place for safekeeping such as ActionForm.xsn. Figure 7-16 shows the completed workflow action form.

The workflow action form

Figure 7.16. The workflow action form

Adding the Item Schema

When the action form is presented to the user, we will need to populate it with the data used in the association and initiation forms. In order to do this, we must create a schema that represents the incoming data and attach it to the action form as a secondary data source. This will allow the form to properly display the information. The process for defining the secondary data source requires you to create a very specific XML file and then attach it to the form. The format of this file is dictated by the workflow infrastructure.

Follow these steps to create and attach the schema:

  1. Open a copy of Notepad and enter the following code. This code defines the fields that are available from the association and initiation forms:

    <z:row xmlns:z="#RowsetSchema" ows_managerUsername="" ows_managerFullname=""
     ows_reviewType="" ows_reviewComments="" />
  2. Save the file as ItemMetadata.xml to an appropriate location. You will only need this file temporarily while you define the secondary data source, but the file must be named ItemMetadata.xml.

  3. Open ActionForm.xsn in design mode if it's not already open.

  4. In the Design Tasks pane, click the Data Source link.

  5. Click the Manage Data Connections link.

  6. In the Data Connections dialog, click the Add button.

  7. In the data connection wizard, select to Create a Connection to Receive Data.

  8. Click the Next button.

  9. On the next screen of the wizard, select the XML Document option and click the Next button.

  10. Click the Browse button.

  11. In the Open dialog, locate the ItemMetadata.xml file, select it, and click the Open button.

  12. Click the Next button.

  13. On the next screen of the wizard, ensure that the option labeled Include the Data Source as a Resource File in the Form Template or Template Part is selected.

  14. Click the Next button.

  15. On the next screen, ensure the box labeled Automatically Retrieve Data When Form Is Opened is checked and that the connection is named ItemMetadata. Then click the Finish button.

  16. In the Data Connections dialog, click the Close button.

  17. In the Action form, right-click the ReviewNotes text box and select Properties from the context menu.

  18. Click the formula (fx) button under the Default Value section.

  19. In the Insert Formula dialog, click the Insert Field or Group button.

  20. In the Select a Field or Group dialog, select ItemMetadata (secondary) from the drop-down list.

  21. Select the ows_reviewComments node and click the OK button.

  22. In the Insert Formula dialog, click the OK button.

  23. In the Field or Group Properties dialog, click the OK button.

  24. Save the form and close InfoPath.

Creating the Workflow Project

The Visual Studio project you create for a workflow implements all of the functionality required to automate a process. In this exercise, you will simply be assigning a task to the manager to perform the employee review. Although you have the full power of the .NET Framework to use in your workflow, task assignment is a fundamental part of SharePoint workflows. Remember that human workflows in SharePoint typically assign tasks to users and then wait for them to be marked as complete.

Follow these steps to create the workflow project:

  1. Start Visual Studio 2005.

  2. From the main menu, select File

    Creating the Workflow Project
  3. In the New Project dialog, click on SharePoint Server in the Project Types tree.

  4. Select SharePoint Sequential Workflow Library in the Templates window.

  5. Enter ReviewFlow in the Name field and pick a suitable location to develop the project.

  6. Click the OK button.

  7. When the project is created, save it and close Visual Studio. You must perform a few additional steps with the InfoPath forms before you can proceed to code the project.

Publishing the Forms

Once you have created the workflow project, you must publish the workflow forms to the project directory. This is required to support the deployment of the forms as part of the workflow feature you will create. This will allow you to deploy the forms along with the workflow assembly.

Follow these steps to publish the forms:

  1. Open the form AssocForm.xsn in design mode in InfoPath.

  2. In the Design Tasks pane, click the Design Checker link.

  3. In the Design Checker pane, click the Change Compatibility Settings link.

  4. In the Form Options dialog, check the box labeled Design a Form Template That Can Be Opened in a Browser or in InfoPath.

  5. Enter the URL of a server running InfoPath Forms Services (e.g., http://vsmoss) that can be used to validate the form design.

  6. Click the Security and Trust Category.

  7. Uncheck the box labeled Automatically Determine Security Level.

  8. Select the Full Trust option.

  9. Check the box labeled Sign This Form Template.

  10. Click the Create Certificate button.

  11. Click OK to acknowledge the message regarding self-signed certificates.

  12. Click the OK button.

  13. In the Design Checker pane, click the Design Tasks link.

  14. In the Design Tasks pane, click the Publish Form Template link.

  15. In the first step of the Publishing Wizard, select to publish the form to a network location and click the Next button.

  16. In the next step, click the Browse button.

  17. In the Browse dialog, locate the directory where the file Workflow1.cs resides. This is the folder where the workflow project was created. The workflow forms must be published here.

  18. Enter AssocForm.xsn in the File Name field and click the OK button.

  19. In the Publishing Wizard, click the Next button.

  20. In the next screen, clear the alternate access path and click the Next button. The workflow forms must only be accessed through the workflow process in SharePoint.

  21. On the final screen, click the Publish button.

  22. Close the wizard.

  23. Repeat steps 1 through 10 to publish the InitForm.xsn and the ActionForm.xsn, only you should use the certificate you already created to sign the form instead of creating a new one.

Developing the Project

Developing the workflow project is a combination of placing activities on the workflow design canvas and writing code behind them. Each activity you place on the canvas must be configured using various properties and then associated with a method in the code. In this project, you use the activities and code to receive information from the initialization form and create a new task.

Follow these steps to get started:

  1. Open the ReviewFlow project in Visual Studio 2005.

  2. In the Solution Explorer, right-click Workflow1.cs and select View Designer from the context menu.

  3. Right-click the WorkflowActivated1 activity and select Generate Handlers from the context menu. This will create the handler that runs when the workflow is started. You will code this later.

  4. In the Solution Explorer, right-click Workflow1.cs and select View Designer from the context menu to display the design canvas again.

  5. Right-click the Toolbox in Visual Studio and select Choose Items.

  6. In the Choose Toolbox Items dialog, click the .NET Framework Components tab.

  7. Select the CompleteTask, CreateTask, and OnTaskChanged workflow activities. These are the activities that you will use to create and assign a new task for the workflow.

  8. Click the OK button to add these activities to the toolbox.

The CreateTask Activity

This workflow begins by creating a task and assigning values to it. Creating the task is done by using the CreateTask activity. This activity must be configured so that it is properly managed by the workflow activity and has a unique identifier and a mechanism for accessing the fields in the task item. In this section, you will use the CreateTask activity to create the new task item:

  1. Drag a CreateTask activity from the toolbox and drop it on the node just below the onWorkflowActivated1 activity.

  2. Right-click createTask1 and select Properties from the context menu.

  3. In the Properties window, set the CorrelationToken property to taskToken and hit the Enter key. This token is used to keep track of the particular task item in the workflow as multiple instances of the workflow are run by SharePoint. This token must be shared by all activities involved with the task item.

  4. In the Properties window, set the OwnerActivityName to Workflow1. This field can be found directly below the CorrelationToken property you set earlier. This is the name of the workflow class that was created for your project. Assigning the task to this activity ensures that the lifetime of the process components is properly managed.

  5. In the Properties window, click the ellipses associated with the TaskId property.

  6. In the Bind dialog, click the Bind to a New Member tab.

  7. On the Bind to a New Member tab, select the Create Field option. This will create a simple member variable in your code. The taskId variable will uniquely identify the task you are creating.

  8. Enter taskId in the New Member Name field and click the OK button.

  9. In the Properties window, click the ellipses associated with the TaskProperties property.

  10. In the Bind dialog, click the Bind to a New Member tab.

  11. On the Bind to a New Member tab, select the Create Field option. This will create a simple member variable in your code. The taskProperties variable will contain information about the task that is created.

  12. Enter taskProperties in the New Member Name field and click the OK button.

  13. On the design canvas, right-click the CreateTask activity and select Generate Handlers from the context menu. This will create a new method that runs when the task is created. You will code this later. Figure 7-17 shows the Workflow Designer as it should appear at this point with the key property values visible. Check your work carefully against this image to avoid issues later.

The CreateTask activity

Figure 7.17. The CreateTask activity

The While Activity

Once the task is assigned, the workflow must wait until the user completes it. The While activity establishes a loop that checks the task to see if it is complete before continuing. The While activity will contain an OnTaskChanged activity that you will use to check the task to see if it is completed after it has been changed.

Follow these steps to wait for the task to complete:

  1. Drag a While activity from the toolbox and drop it on the node just below the createTask1 activity.

  2. Right-click the While activity and select Properties from the context menu.

  3. In the Properties window, set the Condition property named DynamicUpdateCondition to CodeCondition. This will allow you to specify a variable in code that will be used to determine whether the task is completed.

  4. Expand the DynamicUpdateCondition property and type notComplete in the field and hit the Enter key. This will create a method you can use to determine whether the task is completed. You'll code this method later.

  5. Click the Workflow1.cs [Design] tab to return to the design canvas.

  6. Drag an OnTaskChanged activity from the toolbox and drop it on the While activity.

  7. Right-click the onTaskChanged1 activity and select Properties from the context menu.

  8. In the Properties window, set the CorrelationToken property to taskToken.

  9. In the Properties window, click the ellipses associated with the AfterProperties property.

  10. In the Bind dialog, click the Bind to a New Member tab.

  11. Enter afterProperties in the New Member Name field.

  12. Select the Create Field option to create a member variable that will allow you to access the values of the task item after it has been changed.

  13. Click the OK button.

  14. In the Properties window, click the ellipses associated with the BeforeProperties property.

  15. In the Bind dialog, click the Bind to a New Member tab.

  16. Enter beforeProperties in the New Member Name field.

  17. Select the Create Field option to create a member variable that will allow you to access the values of the task item before it has been changed.

  18. Click the OK button.

  19. In the Properties window, click the ellipses associated with the TaskId property.

  20. In the Bind dialog, click the Bind to an Existing Member tab.

  21. Select taskId from the list and click the OK button.

  22. On the design canvas, right-click the OnTaskChanged activity and select Generate Handlers from the context menu. This will create a new method that runs when the task is changed. You will code this later. Figure 7-18 shows the Workflow Designer as it should appear at this point with the key property values visible. Check your work carefully against this image to avoid issues later.

The While activity

Figure 7.18. The While activity

The CompleteTask Activity

The CompleteTask activity is used to take action after the task is completed. Using this activity would allow you to initiate further steps in the process or interact with other systems. In this exercise, you'll simply add the activity to complete the workflow.

Follow these steps to add the CompleteTask activity:

  1. Drag a CompleteTask activity from the toolbox and drop it just below the While activity.

  2. Right-click the CompleteTask activity and select Properties from the context menu.

  3. In the Properties window, set the CorrelationToken property to taskToken.

  4. In the Properties window, click the ellipses associated with the TaskId property.

  5. In the Bind dialog, click the Bind to an Existing Member tab.

  6. Select taskId from the list and click the OK button.

  7. On the design canvas, right-click the TaskComplete activity and select Generate Handlers from the context menu. This will create a new method that runs when the task is completed. You will code this later. Figure 7-19 shows the Workflow Designer as it should appear.

The CompleteTask activity

Figure 7.19. The CompleteTask activity

Coding the Project

Once you have finished configuring the activities on the designer, you are ready to code the workflow. Coding the workflow involves retrieving the initialization data, settings task properties, and waiting for the task to complete. I have provided the complete solution code in Listing 7-14 for reference. You should add the bolded sections to your code. I discuss the key parts of the code in the following sections.

Example 7.14. The Complete Workflow Code

//Included in template
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Xml.Serialization;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;
using Microsoft.Office.Workflow.Utility;

//Added
using System.Diagnostics;

namespace ReviewFlow
{
  public sealed partial class Workflow1: SequentialWorkflowActivity
  {
    public Workflow1()
    {
      InitializeComponent();
    }

    //Workflow activated
    public Guid workflowId = default(System.Guid);
    public string managerUsername = default(string);
    public string managerFullname = default(string);
    public string reviewType = default(string);
    public string reviewComments = default(string);

    public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties
    workflowProperties = new
      Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();

    private void onWorkflowActivated1_Invoked(
      object sender, ExternalDataEventArgs e)
    {
      try
      {
        //Save the identifier for the workflow
        workflowId = workflowProperties.WorkflowId;

        // InitiationData comes from the initialization form
        XmlDocument document = new XmlDocument();
        document.LoadXml(workflowProperties.InitiationData);

        XmlNamespaceManager ns = new XmlNamespaceManager(document.NameTable);
        ns.AddNamespace("my",
    "http://schemas.microsoft.com/office/infopath/2003/myXSD/2006-10-25T13:38:03");
                managerUsername =
document.SelectSingleNode("/my:flowFields/my:managerUsername", ns).InnerText;
                managerFullname =
     document.SelectSingleNode("/my:flowFields/my:managerFullname", ns).InnerText;
                reviewType =
     document.SelectSingleNode("/my:flowFields/my:reviewType", ns).InnerText;
                reviewComments =
     document.SelectSingleNode("/my:flowFields/my:reviewComments", ns).InnerText;
    }
    catch (Exception x)
    {
      logMessage(x.Message, EventLogEntryType.Error);
    }
  }

    //Task Created
    public Guid taskId = default(System.Guid);
    public SPWorkflowTaskProperties taskProperties = new
      Microsoft.SharePoint.Workflow.SPWorkflowTaskProperties();

    private void createTask1_MethodInvoking(object sender, EventArgs e)
    {
      try
      {
        // Create unique task ID
        taskId = Guid.NewGuid();

        //Set task properties
        taskProperties.AssignedTo = managerUsername;
        taskProperties.Description = reviewComments + "/n";
        taskProperties.Title = "Perform employee review [" + reviewType + "]";

        //Populate the action form using the secondary data source
        taskProperties.ExtendedProperties["reviewComments"] = reviewComments;
      }
      catch (Exception x)
      {
        logMessage(x.Message, EventLogEntryType.Error);
      }

    }

    //Looping
    private bool complete = false;
    private void notComplete(object sender, ConditionalEventArgs e)
    {
      e.Result = !complete;
    }
//Task Changed
    public SPWorkflowTaskProperties afterProperties = new
      Microsoft.SharePoint.Workflow.SPWorkflowTaskProperties();
    public SPWorkflowTaskProperties beforeProperties = new
      Microsoft.SharePoint.Workflow.SPWorkflowTaskProperties();

    private void onTaskChanged1_Invoked(object sender, ExternalDataEventArgs e)
    {
      try
      {
        complete =
      bool.Parse(afterProperties.ExtendedProperties["reviewCompleted"].ToString());
      }
      catch (Exception x)
      {
        logMessage(x.Message, EventLogEntryType.Error);
      }
    }

    //Task Completed
    private void completeTask1_MethodInvoking(object sender, EventArgs e)
    {
      try
      {
        afterProperties.Description +=
          afterProperties.ExtendedProperties["reviewNotes"].ToString();
        afterProperties.PercentComplete = 100;
      }
      catch (Exception x)
      {
        logMessage(x.Message, EventLogEntryType.Error);
      }
    }

    //Logging
    private void logMessage(string message, EventLogEntryType type)
    {
      if (!EventLog.SourceExists("SharePoint Workflow"))
        EventLog.CreateEventSource("SharePoint Workflow", "Application");
      EventLog.WriteEntry("SharePoint Workflow", message, type);
    }
  }
}

Coding onWorkflowActivated1_Invoked

This method runs when the workflow is started. The InitiationData property of the workflowProperties object contains the XML data from the initialization form. Therefore, you can load this into an XmlDocument object and retrieve all of the values from the form. In this exercise, you simply store these values in variables for later use.

When you code this part of the solution, be sure to use the correct namespace and XPath values. Yours may be different than what is shown in Listing 7-14. You can get the XPath for any field by right-clicking it in the task pane in InfoPath and selecting Copy XPath from the context menu. I demonstrate this technique in detail in Chapter 6. In order to get the namespace for the form, select Properties from the same menu and click the Details tab on the Properties dialog.

You should also notice that I have placed error handling in all of the routines. If an error occurs, I log it to the Application event log. This technique can save you hours of painful debugging.

Coding createTask1_MethodInvoking

This method runs when a new task needs to be created. In this case, you are simply setting the values for the task item. Additionally, you use the ExtendedProperties collection to access the action form. Since the action form is associated with the task item, the extended properties will let you read or write to the form fields.

Coding the Loop

The notComplete method runs whenever the loop needs to check and see if it should continue or break. This code simply returns the opposite of the reviewCompleted field. This field is saved to a member variable in the TaskChanged1_MethodInvoked method.

Coding completeTask1_MethodInvoking

This method runs when the task is completed. In this code, the task is updated with the notes from the review. It is also set to be 100% complete. After this code runs, the workflow will show that it is completed in SharePoint.

Building the Project

Building the project is a matter of signing the assembly and building it. When you sign the assembly, be sure to record the PublicKeyToken for use in the Feature.xml file. Remember, you can obtain the PublicKeyToken for any assembly using the strong-name utility sn.exe with the -T option.

Deploying the Workflow

The workflow must be deployed as a feature. Therefore, you must create a Feature.xml and manifest file. The workflow project template provides a Feature.xml file and a manifest file named Workflow.xml. You can use these as starting points for creating the files. Additionally, Microsoft has provided a set of snippets that you can use to create the Feature.xml and Workflow.xml files. Although these snippets should be available after the MOSS SDK is installed, they never seem to be.

Follow these steps to get the snippets working:

  1. Open Feature.xml in Visual Studio.

  2. Press Ctrl

    Deploying the Workflow
  3. In the Code Snippets Manager dialog, select XML from the Language drop-down list.

  4. Click the Add button to search for the workflow snippets.

  5. In the Code Snippets Directory dialog, navigate to C:Program FilesMicrosoft Visual Studio 8Xml1033SnippetsSharePoint Server Workflow.

  6. Click the Open button.

  7. In the Code Snippets Manager, click the OK button.

  8. In the Feature.xml file, right-click the body of the document and select Insert Snippet from the context menu.

  9. Select SharePoint Server Workflow

    Deploying the Workflow

The Feature.xml file is similar to ones that I discuss earlier in the chapter. The ReceiverAssembly and ReceiverClass attributes are used to reference the workflow infrastructure and are always the same. The ElementManifests element references the manifest for the feature, but also includes a separate ElementFile element for each of the three forms that you must deploy. Additionally, Property elements are used to register the InfoPath forms in the central form repository and designate them as appropriate for use with workflows. In the Solution Explorer, open the Feature.xml file and replace all of the code with the code in listing 7-15.

Example 7.15. The Feature.xml File

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="53FF8395-A83A-4bfd-922C-E11412E0919A"
  Title="Review Process Workflow"
  Description="Automated review process"
  Version="1.0.0.0"
  Scope="Site"
  ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0,
    Culture=neutral, PublicKeyToken=71e9bce111e9429c"
  ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver"
  xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
  <ElementManifest Location="workflow.xml" />
  <ElementFile Location="AssocForm.xsn"/>
  <ElementFile Location="InitForm.xsn"/>
  <ElementFile Location="Actionform.xsn"/>
</ElementManifests>
<Properties>
<Property Key="GloballyAvailable" Value="true" />
  <Property Key="RegisterForms" Value="*.xsn" />
</Properties>
</Feature>

The Workflow.xml file is also similar to ones that you have created before. The AssociationUrl, InstantiationUrl, and ModificationUrl attributes refer to the pages that will host your InfoPath forms in the workflow. Additionally, there are specific identifiers included in the MetaData section for each form. These are the unique identifiers assigned by InfoPath and they can be retrieved by opening the form in design mode and selecting File

The Feature.xml File

Example 7.16. The Workflow.xml File

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Workflow
  Name="Review Process Workflow"
  Description="Automated review process"
  Id="8A5252A3-D1B2-4aff-A5BF-9D91D2BF7D67"
  CodeBesideClass="ReviewFlow.Workflow1"
  CodeBesideAssembly="ReviewFlow, Version=1.0.0.0, Culture=neutral,
     PublicKeyToken=689f1d0ba493bcce"
  TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"
  AssociationUrl="_layouts/CstWrkflIP.aspx"
  InstantiationUrl="_layouts/IniWrkflIP.aspx"
  ModificationUrl="_layouts/ModWrkflIP.aspx">
  <Categories/>
  <MetaData>
    <Association_FormURN>
     urn:schemas-microsoft-com:office:infopath:AssocForm:-myXSD-2006-10-25T13-38-03
    </Association_FormURN>
    <Instantiation_FormURN>
     urn:schemas-microsoft-com:office:infopath:InitForm:-myXSD-2006-10-25T13-38-03
    </Instantiation_FormURN>
    <Task0_FormURN>
     urn:schemas-microsoft-com:office:infopath:ActionForm:-myXSD-2006-10-26T11-03-06
    </Task0_FormURN>
    <StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>
  </MetaData>
</Workflow>
</Elements>

Once you have the files created, you can deploy the feature in the standard way. Copy all of the InfoPath form files, Feature.xml, and Workflow.xml to a directory named ReviewFlow underneath the FEATURES directory. Install the ReviewFlow.dll assembly in the GAC and use STSADM.EXE to install the new feature. The workflow project template also has a postbuild batch file you can use to perform these steps automatically. If you want to use this batch file, you should refer to the instructions inside the file, which is named PostBuildActions.bat.

Tip

If you have errors and need to redeploy the workflow, deactivate and uninstall the workflow completely. Then make your changes and try again. Never force a workflow to install over an existing installation because it can cause issues with the InfoPath form versioning that may prevent the workflow from executing.

Using the Workflow

Once the workflow is activated, you may use it with a list. For this exercise, you will use it with a Contacts list, but you could just as easily use it with an InfoPath form or a Word document. Open the site where you activated the workflow and create a new Contacts list named Employees and add some items. Once you have done this, you can associate the workflow with the list.

Follow these steps to use the workflow:

  1. On the Employees page, select Settings

    Using the Workflow
  2. On the Customize page, click the link titled Workflow Settings.

  3. On the Add a Workflow page, select the Review Process Workflow from the list of available workflows.

  4. Type Employee Review Workflow in the Name field. This will be the name of the association.

  5. Select New Task List from the Task List section. This will create a new task list for assignments associated with this workflow.

  6. Select New History List from the History List section. This will create a new list used to track workflow milestones for auditing and reporting.

  7. Leave the option checked to allow manual initiation of a workflow.

  8. Click the OK button.

Once the association is completed, you should be able to start a performance review for a person on the Employees list. Verify that your initialization form appears and that a new task is created. Then complete the task and verify it works as expected. Be sure to check the event log for any errors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset