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.
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.
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 |
---|---|---|---|
| No |
| Determines whether the feature is active by default when a new web application is created. The default value is |
| No |
| 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 |
| No |
| Determines whether the feature is activated by default in the Central Administration site. The default value is |
| No | Text | Identifies the feature creator. This element is optional. |
| 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. |
| No | Text | Describes of the feature that will appear on the Feature list in SharePoint. |
| No |
| 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 |
| Yes | Text | Provides a unique identifier for the feature. Typically, this must be a |
| No | Text | Associates an image file with a feature. The image appears in the Feature list alongside the feature description. |
| No | Text | Provides alternate text for the feature image. |
| 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." |
| No | Class name | Identifies the class name of the class that will receive activation and deactivation events for the feature. |
| No |
| Indicates whether supporting resources are required for this feature. The default value is |
| Yes |
| Determines on what list the feature appears and therefore at what scope it can be activated. |
| No |
| Specifies the solution to which the feature belongs. I cover creating solutions in Chapter 10. |
| No | Text | Specifies the title of the feature as it appears in SharePoint. |
| No | Text | Specifies the version number in |
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>
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.
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
.
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
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 |
| N/A |
Edit form toolbar |
| N/A |
New form toolbar |
| N/A |
List view toolbar |
| N/A |
List item menu |
| N/A |
Library/List New menu |
|
|
Library/List Actions menu |
|
|
Library/List Settings menu |
|
|
Library Upload menu |
|
|
Site Actions menu |
|
|
Site Settings page, Site collection administration links |
|
|
Site Settings page, Site administration links |
|
|
Site Settings page, Galleries links |
|
|
Site Settings page, Look and feel links |
|
|
Site Settings page, Users and permissions links |
|
|
Site Actions menu for surveys |
|
|
Site Settings page, links for surveys |
|
|
Content type settings links |
| N/A |
Central Administration Operations page |
| N/A |
Central Administration Application Management page |
| 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.
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.
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.
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} |
|
{RecurrenceId} | Recurrence index of the item. |
| Site-relative link. |
| Site collection-relative link. |
{SiteUrl} | URL of the site where the action occurred. |
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 |
---|---|
| Generic list |
| Document library |
| Survey |
| Links list |
| Announcements list |
| Contacts list |
| Events list |
| Tasks list |
| Discussion board |
| Picture library |
| Data sources |
| Site Template Gallery |
| Web Part Gallery |
| List Template Gallery |
| Form library |
| Publishing library |
| Custom list |
| Meeting Series list |
| Meeting Agenda list |
| Meeting Attendees list |
| Meeting Decisions list |
| Meeting Objectives list |
| Meeting text box |
| Meeting Things to Bring list |
| Meeting Workspace Pages list |
| Portal Sites list |
| Issue tracking |
| Personal document library |
| 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>
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.
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.
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>
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 |
---|---|---|
|
| Fires after a site collection has been deleted |
|
| Fires just before a site collection is deleted |
|
| Fires after a site has been deleted |
|
| Fires just before a site has been deleted |
|
| Fires after a site has been moved |
|
| Fires just before a site is moved |
|
| Fires after a new field is added to the list |
|
| Fires just before a pending new field is added to the list |
|
| Fires after a field is deleted from the list |
|
| Fires just before a field is deleted from the list |
|
| Fires after a field is updated |
|
| Fires just before a field is updated |
|
| Fires after a new item is added to the list |
|
| Fires just before a pending new item is added to the list |
|
| Fires after an item is deleted from the list |
|
| Fires just before an item is deleted from the list |
|
| Fires after a list item is updated |
|
| Fires just before a list item is updated |
|
| Fires after an attachment is added to a list item |
|
| Fires just before a pending attachment is added to a list item |
|
| Fires after an attachment is deleted from a list item |
|
| Fires just before an attachment is deleted from a list item |
|
| Fires after an item is checked in |
|
| Fires after an item is checked out |
|
| Fires just before an item is checked out |
|
| Fires after an item checkout has been canceled |
|
| Fires just before an item checkout has been cancelled |
|
| Fires after a document conversion has taken place |
|
| Fires after a file has been moved |
|
| Fires just before a file has been moved |
|
| Fires after an e-mail-enabled list receives a new email |
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.
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 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.
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:
Navigate to any document library in VSMOSS.
On the Document Library page, select Settings
On the Customize page, click the link titled Workflow Settings.
On the Add a Workflow page, select the Disposition Approval workflow from the list of available workflows.
Type Retain or Delete in the Name field. This will be the name of the association.
Select New Task List from the Task List section. This will create a new task list for assignments associated with this workflow.
Select New History List from the History List section. This will create a new list used to track workflow milestones for auditing and reporting.
Leave the option checked to allow manual initiation of a workflow, but note that you can automatically start workflows as well.
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:
Navigate to the Retain or Delete Tasks list.
Using the drop-down menu associated with the task item, select Edit Item.
Select the option labeled Do Not Delete This Item.
Add some comments in the text area.
Click the OK button.
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:
Navigate to the Document Library where you created the Retain or Delete association.
On the Document Library page, select Settings
On the Customize page, click the link titled Workflow Settings.
On the Change Workflow Settings page, click the link titled View Workflow Reports.
On the View Workflow Reports page, click the Activity Duration Report link under the Retain or Delete workflow.
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.
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.
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.
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.
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 |
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 |
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.
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.
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
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.
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.
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.
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.
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.
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
.
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.
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:
Start Microsoft InfoPath.
In the Getting Started dialog, click the link titled Design a Form Template.
In the Design a Form Template dialog, select a Blank template and click the OK button to start with a blank form.
When the blank form opens, click the Data Source link in the Design Tasks pane.
Right-click the myFields node and select Properties from the context menu.
Rename the node flowFields in the Name text box and click the OK button.
Right-click the flowFields node and select Add.
In the Add Field or Group dialog, type managerUsername in the Name field and click the OK button.
Right-click the flowFields node and select Add.
In the Add Field or Group dialog, type managerFullname in the Name field and click the OK button.
Right-click the flowFields node and select Add.
In the Add Field or Group dialog, type reviewType in the Name field and click the OK button.
Right-click the flowFields node and select Add.
In the Add Field or Group dialog, type reviewComments in the Name field and click the OK button.
Click on the Design Tasks link at the top of the Data Source pane.
Click the Layout link in the Design Tasks pane.
Drag the Custom Table layout onto the blank form.
In the Insert table dialog, enter 1 in the Columns field and 3 in the Rows field, and click the OK button.
Click the Design Tasks link at the top of the Layout pane.
Click the Controls link in the Design Tasks pane.
Uncheck the box labeled Automatically Create Data Source.
Drag a text box from the Controls pane and drop it in the top cell of the table.
In the Text Box Binding dialog, select the managerUsername field and click the OK button.
Drag a text box from the Controls pane and drop it in the middle cell of the table.
In the Text Box Binding dialog, select the managerFullname field and click the OK button.
Drag a button from the Controls pane and drop it in the bottom cell of the table.
Right-click the button and select Button Properties from the context menu.
Enter Done in the Label field.
Click the Rules button.
In the Rules dialog, click the Add button.
In the Rules dialog, click the Add Action button.
In the Action dialog, select Submit Using a Data Connection from the drop-down list.
Click the Add button.
In the data connection wizard, select to Create a New Connection to Submit Data and click the Next button.
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.
Click the Next button.
On the next screen, name the connection Association and click the Finish button.
In the Action dialog, click the OK button.
In the Rules dialog, click the Add Action button.
Select Close the Form from the drop-down list.
Uncheck the box labeled If Changes Have Not Been Saved, Prompt the User to Save.
Click the OK button.
In the Rule dialog, click the OK button.
In the Rules dialog, click the OK button.
In the Button Properties dialog, click the OK button.
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.
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:
Open AssocForm.xsn
in Design mode in InfoPath 2007 if it is not already open.
Select File
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.
Remove the text box controls for the managerUsername and managerFullname fields from the form. Do not delete the button.
Click the Design Tasks link at the top of the task pane.
Click the Controls link in the Design Tasks pane.
Uncheck the box labeled Automatically Create Data Source.
Drag a text box from the Controls pane and drop it in the top cell of the table.
In the Text Box Binding dialog, select the reviewType field and click the OK button.
Drag a text box from the Controls pane and drop it in the middle cell of the table.
In the Text Box Binding dialog, select the reviewComments field and click the OK button.
Save the initiation form. Figure 7-15 shows the final 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:
Start Microsoft InfoPath.
In the Getting Started dialog, click the link titled Design a Form Template.
In the Design a Form Template dialog, select a Blank template and click the OK button.
When the blank form opens, click the Data Source link in the Design Tasks pane.
Right-click the myFields node and select Properties from the context menu.
Rename the node actionFields in the Name text box and click the OK button.
Right-click the actionFields node and select Add.
In the Add Field or Group dialog, type reviewCompleted in the Name field.
Select True/False (Boolean) from the Data Type drop-down list and click the OK button.
Right-click the actionFields node and select Add.
In the Add Field or Group dialog, type reviewNotes in the Name field and click the OK button.
Click the Design Tasks link at the top of the Data Source pane.
Click the Layout link in the Design Tasks pane.
Drag the Custom Table layout onto the blank form.
In the Insert Table dialog, enter 1 in the Columns field and 3 in the Rows field, and click the OK button.
Click the Design Tasks link at the top of the Layout pane.
Click the Controls link in the Design Tasks pane.
Uncheck the box labeled Automatically Create Data Source.
Drag a check box from the Controls pane and drop it in the top cell of the table.
In the Check Box Binding dialog, select the reviewCompleted field and click the OK button.
Drag a text box from the Controls pane and drop it in the bottom cell of the table.
In the Text Box Binding dialog, select the reviewNotes field and click the OK button.
Drag a button from the Controls pane and drop it in the bottom cell.
Right-click the button and select Button Properties from the context menu.
Enter Done in the Label field.
Click the Rules button.
In the Rules dialog, click the Add button.
In the Rules dialog, click the Add Action button.
In the Action dialog, select Submit Using a Data Connection from the drop-down list.
Click the Add button.
In the data connection wizard, select to create a new data connection to submit data and click the Next button.
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.
Click the Next button.
Name the connection Action and click the Finish button.
In the Action dialog, click the OK button.
In the Rules dialog, click the Add Action button.
Select Close the Form from the drop-down list.
Uncheck the box labeled If Changes Have Not Been Saved, Prompt the User to Save.
Click the OK button.
In the Rule dialog, click the OK button.
In the Rules dialog, click the OK button.
In the Button Properties dialog, click the OK button.
Save the form to an appropriate place for safekeeping such as ActionForm.xsn
. Figure 7-16 shows the completed 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:
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="" />
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
.
Open ActionForm.xsn
in design mode if it's not already open.
In the Design Tasks pane, click the Data Source link.
Click the Manage Data Connections link.
In the Data Connections dialog, click the Add button.
In the data connection wizard, select to Create a Connection to Receive Data.
Click the Next button.
On the next screen of the wizard, select the XML Document option and click the Next button.
Click the Browse button.
In the Open dialog, locate the ItemMetadata.xml
file, select it, and click the Open button.
Click the Next button.
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.
Click the Next button.
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.
In the Data Connections dialog, click the Close button.
In the Action form, right-click the ReviewNotes text box and select Properties from the context menu.
Click the formula (fx) button under the Default Value section.
In the Insert Formula dialog, click the Insert Field or Group button.
In the Select a Field or Group dialog, select ItemMetadata (secondary) from the drop-down list.
Select the ows_reviewComments node and click the OK button.
In the Insert Formula dialog, click the OK button.
In the Field or Group Properties dialog, click the OK button.
Save the form and close InfoPath.
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:
Start Visual Studio 2005.
From the main menu, select File
In the New Project dialog, click on SharePoint Server in the Project Types tree.
Select SharePoint Sequential Workflow Library in the Templates window.
Enter ReviewFlow in the Name field and pick a suitable location to develop the project.
Click the OK button.
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.
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:
Open the form AssocForm.xsn
in design mode in InfoPath.
In the Design Tasks pane, click the Design Checker link.
In the Design Checker pane, click the Change Compatibility Settings link.
In the Form Options dialog, check the box labeled Design a Form Template That Can Be Opened in a Browser or in InfoPath.
Enter the URL of a server running InfoPath Forms Services (e.g., http://vsmoss
) that can be used to validate the form design.
Click the Security and Trust Category.
Uncheck the box labeled Automatically Determine Security Level.
Select the Full Trust option.
Check the box labeled Sign This Form Template.
Click the Create Certificate button.
Click OK to acknowledge the message regarding self-signed certificates.
Click the OK button.
In the Design Checker pane, click the Design Tasks link.
In the Design Tasks pane, click the Publish Form Template link.
In the first step of the Publishing Wizard, select to publish the form to a network location and click the Next button.
In the next step, click the Browse button.
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.
Enter AssocForm.xsn in the File Name field and click the OK button.
In the Publishing Wizard, click the Next button.
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.
On the final screen, click the Publish button.
Close the wizard.
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 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:
Open the ReviewFlow project in Visual Studio 2005.
In the Solution Explorer, right-click Workflow1.cs
and select View Designer from the context menu.
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.
In the Solution Explorer, right-click Workflow1.cs
and select View Designer from the context menu to display the design canvas again.
Right-click the Toolbox in Visual Studio and select Choose Items.
In the Choose Toolbox Items dialog, click the .NET Framework Components tab.
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.
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:
Drag a CreateTask activity from the toolbox and drop it on the node just below the onWorkflowActivated1 activity.
Right-click createTask1 and select Properties from the context menu.
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.
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.
In the Properties window, click the ellipses associated with the TaskId
property.
In the Bind dialog, click the Bind to a New Member tab.
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.
Enter taskId in the New Member Name field and click the OK button.
In the Properties window, click the ellipses associated with the TaskProperties
property.
In the Bind dialog, click the Bind to a New Member tab.
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.
Enter taskProperties in the New Member Name field and click the OK button.
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 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:
Drag a While activity from the toolbox and drop it on the node just below the createTask1 activity.
Right-click the While activity and select Properties from the context menu.
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.
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.
Click the Workflow1.cs [Design] tab to return to the design canvas.
Drag an OnTaskChanged activity from the toolbox and drop it on the While activity.
Right-click the onTaskChanged1 activity and select Properties from the context menu.
In the Properties window, set the CorrelationToken
property to taskToken.
In the Properties window, click the ellipses associated with the AfterProperties
property.
In the Bind dialog, click the Bind to a New Member tab.
Enter afterProperties in the New Member Name field.
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.
Click the OK button.
In the Properties window, click the ellipses associated with the BeforeProperties
property.
In the Bind dialog, click the Bind to a New Member tab.
Enter beforeProperties in the New Member Name field.
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.
Click the OK button.
In the Properties window, click the ellipses associated with the TaskId
property.
In the Bind dialog, click the Bind to an Existing Member tab.
Select taskId from the list and click the OK button.
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 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:
Drag a CompleteTask activity from the toolbox and drop it just below the While activity.
Right-click the CompleteTask activity and select Properties from the context menu.
In the Properties window, set the CorrelationToken
property to taskToken.
In the Properties window, click the ellipses associated with the TaskId
property.
In the Bind dialog, click the Bind to an Existing Member tab.
Select taskId
from the list and click the OK button.
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.
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 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.
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:
Open Feature.xml
in Visual Studio.
Press Ctrl
In the Code Snippets Manager dialog, select XML from the Language drop-down list.
Click the Add button to search for the workflow snippets.
In the Code Snippets Directory dialog, navigate to C:Program FilesMicrosoft Visual Studio 8Xml1033SnippetsSharePoint Server Workflow.
Click the Open button.
In the Code Snippets Manager, click the OK button.
In the Feature.xml
file, right-click the body of the document and select Insert Snippet from the context menu.
Select SharePoint Server 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
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
.
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.
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:
On the Employees page, select Settings
On the Customize page, click the link titled Workflow Settings.
On the Add a Workflow page, select the Review Process Workflow from the list of available workflows.
Type Employee Review Workflow in the Name field. This will be the name of the association.
Select New Task List from the Task List section. This will create a new task list for assignments associated with this workflow.
Select New History List from the History List section. This will create a new list used to track workflow milestones for auditing and reporting.
Leave the option checked to allow manual initiation of a workflow.
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.