Custom Workflow Actions (Part 1 of 3)
A recent SharePoint project on my plate had a specific requirement that every item added to a list must be emailed to a specific email address. At the time of the meeting, this requirement seemed to be a simple requirement. While stating that the requirement was possible, which was based on a vague memory of emailing functionality in the SharePoint Designer workflow action list, I decided to agree on making it happen.<!–more–>
What I did not realize was that the workflow actions in SharePoint Designer are quite basic. I had built up in my mind that the workflow capabilities of SharePoint were powerful from what I heard from so many individuals. What I did not realize was, while the fact that SharePoint workflows are powerful, to make them perform unique tasks it would require considerable involvement.
Unfortunately for me, I had a deadline to complete the task within two days. Thus began the conquering of creating a custom workflow action. Since much of the information I found on the Internet seemed to be missing pieces of the puzzle, here is my attempt to create a complete tutorial on creating a custom workflow action in Visual Studio 2008 that is applicable in SharePoint Designer and Windows SharePoint Services.
Requirements
Microsoft SharePoint Designer 2007, Microsoft Visual Studio 2008, Microsoft Windows SharePoint Services, and patience.
If programming for SharePoint is new to you, patience will serve you well. I was very frustrated for the two days while I figured out and then produced what I am going to explain to you below.
Fundamentals
The workflow functionality of SharePoint is based on the Windows Workflow Foundations (WWF) programming model, therefore if you have experience with WWF you may find programming workflow actions for SharePoint intuitive. Microsoft Windows SharePoint Services (WSS) is built on .NET Framework technology, which we will be utilizing for the workflow action program. If you understand both of these technologies well, creating a custom workflow action will be a breeze.
Creating a Custom Workflow Action
Enough with the preliminary text, let’s get started with creating the custom workflow action.
For the demonstration, we will be creating a custom workflow action that allows you to send an email with the attachments from a specified list in your site. The custom workflow action will allow you to configure four user input fields: (a) the Email Content; (b) the Send From Email; (c) the SMTP Host; and (d) the Associated List.
Although this project can be broken down into three sequential steps, these three steps are so closely intertwined that content from all three are required by the other three. With that stated, read all three steps, play around, then begin your actual development. The three steps are:
Step One: Create and Compile your .NET Workflow Activity
Step Two: Modify or Create .ACTIONS file and Add to Authorized Assemblies
Step Three: Deploy to WSS server and GAC
Step One: Create and Compile your .NET Workflow Activity
Project Environment Setup
Let’s begin by setting up the Visual Studio project and the environment, so open Visual Studio 2008.
In Visual Studio 2008, start a new Project by clicking on File > New Project.
In the New Project window, choose Workflow Activity Library and name the activity the same name you will use for the rest of the project. For the purposes of this demonstration, we will name the project devtrends.EmailAttachments. Click OK.
Since a custom workflow action does not have any graphical interface and in addition we are creating a single workflow activity, go ahead and immediately view the code for the project.
Next, we will add the references to two DLLs that are associated with the installation of Windows SharePoint Services (WSS). These files will reside on the server that runs WSS, which is likely in the path: \\server_name\c$\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI\
Copy the files:
Microsoft.SharePoint.dll
Microsoft.SharePoint.xml
Microsoft.sharepoint.WorkflowActions.dll
Microsoft.sharepoint.WorkflowActions.xml
to your local computer, using the destination location, C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI\.
Now that those files are copied to your local development computer, return to the Visual Studio project and add these two DLLs as references.
Now, add a few “using” statements to include the required namespaces. The namespace list for this project is as shown below, with the additional statements added at the end of the “using” line up.
Just two more changes, as shown as below: (a) change the name of the partial class to “SentEmailwithListAttachments” by modifying the first line to be (ensure that you are not editing the Designer.cs file); and (b) Add the five Using statements, Microsoft.SharePoint, Microsoft.SharePoint.Workflow, Microsoft.SharePoint.WorkflowActions, System.Net.Mail and System.IO.
Now that we are ready to create the SharePoint Workflow Activity, let’s begin by copying and pasting the following code into your partial class, SendEmailwithListAttachments:
#region DepedencyProperties for the Activity
public static DependencyProperty BCCProperty = DependencyProperty.Register(“BCC”, typeof(ArrayList), typeof(SentEmailwithListAttachments));
public static DependencyProperty BodyProperty = DependencyProperty.Register(“Body”, typeof(string), typeof(SentEmailwithListAttachments));
public static DependencyProperty CCProperty = DependencyProperty.Register(“CC”, typeof(ArrayList), typeof(SentEmailwithListAttachments));
public static DependencyProperty SubjectProperty = DependencyProperty.Register(“Subject”, typeof(string), typeof(SentEmailwithListAttachments));
public static DependencyProperty ToProperty = DependencyProperty.Register(“To”, typeof(ArrayList), typeof(SentEmailwithListAttachments));
public static DependencyProperty FromUserProperty = DependencyProperty.Register(“FromUser”, typeof(string), typeof(SentEmailwithListAttachments));
public static DependencyProperty SMTPServerNameProperty = DependencyProperty.Register(“SMTPServerName”, typeof(string), typeof(SentEmailwithListAttachments));
public static DependencyProperty __ContextProperty = DependencyProperty.Register(“__Context”, typeof(WorkflowContext), typeof(SentEmailwithListAttachments));
public static DependencyProperty ListIdProperty = DependencyProperty.Register(“ListId”, typeof(string), typeof(SentEmailwithListAttachments));
public static DependencyProperty ListItemProperty = DependencyProperty.Register(“ListItem”, typeof(int), typeof(SentEmailwithListAttachments));
#endregion
#region Properties
[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context
{
get
{
return (WorkflowContext)(base.GetValue(__ContextProperty));
}
set
{
base.SetValue(SentEmailwithListAttachments.__ContextProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public string ListId
{
get
{
return (string)(base.GetValue(ListIdProperty));
}
set
{
base.SetValue(SentEmailwithListAttachments.ListIdProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public int ListItem
{
get
{
return (int)(base.GetValue(ListItemProperty));
}
set
{
base.SetValue(SentEmailwithListAttachments.ListItemProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public string FromUser
{
get
{
return (string)base.GetValue(FromUserProperty);
}
set
{
base.SetValue(FromUserProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public string SMTPServerName
{
get
{
return (string)base.GetValue(SMTPServerNameProperty);
}
set
{
base.SetValue(SMTPServerNameProperty, value);
}
}
[ValidationOption(ValidationOption.Optional)]
public string Body
{
get
{
return (string)base.GetValue(BodyProperty);
}
set
{
base.SetValue(BodyProperty, value);
}
}
[ValidationOption(ValidationOption.Optional)]
public ArrayList CC
{
get
{
return (ArrayList)base.GetValue(CCProperty);
}
set
{
base.SetValue(CCProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public string Subject
{
get
{
return (string)base.GetValue(SubjectProperty);
}
set
{
base.SetValue(SubjectProperty, value);
}
}
[ValidationOption(ValidationOption.Required)]
public ArrayList To
{
get
{
return (ArrayList)base.GetValue(ToProperty);
}
set
{
base.SetValue(ToProperty, value);
}
}
#endregion
#region Initialization
public SentEmailwithListAttachments()
{
InitializeComponent();
}
#endregion
#region Overidden Method (ActivityExecutionStatus)
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
try
{
//Send the email
//grab the Activity for the Lookup Fields to be based on
Activity myActivity = executionContext.Activity;
while (myActivity.Parent != null)
{
myActivity = myActivity.Parent;
}
//Send the email with attachments!!
SendEmail(myActivity);
}
finally
{
//nada
}
//Indicate the activity has closed
return ActivityExecutionStatus.Closed;
}
#endregion
#region SendEMail
private void SendEmail(Activity parent)
{
//STEP 1: Create a new object that will send the email
System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage();
//STEP 2: Create the fromAddress variable and then determine the from email address
//Create an object to specify the from address for the email
System.Net.Mail.MailAddress fromAddress;
try
{
//set the email address
fromAddress = new System.Net.Mail.MailAddress(FromUser);
}
catch (System.Exception Ex)
{
fromAddress = new System.Net.Mail.MailAddress(“no-reply@domain.com”);
}
//Set the from email address on the message object
msg.From = fromAddress;
//STEP 3: Determine who the email will be sent to, To and CC, and add those email addresses to the msg object created in STEP 1.
try
{
//Add the email addresses stored in the To property to the email message object
for (int i = 0; i < To.Count; i++)
{
msg.To.Add(To[i].ToString());
}
}
catch (System.Exception Ex)
{
//errored out on the To list
msg.To.Add(“administrator@domain.com”);
}
try
{
//Add the email addresses stored in the CC property to the email message object
if (CC != null)
{
for (int i = 0; i < CC.Count; i++)
{
msg.CC.Add(CC[i].ToString());
}
}
}
catch (System.Exception Ex)
{
//errored out on the CC list… odd
msg.CC.Add(“administrator@domain.com”);
}
//STEP 4: Create the subject line for this Email
String mySubject = “”;
try
{
//To include fields in the subject line from the List, uncomment the lines below
//mySubject += Subject;
//mySubject += ” (” + Helper.LookupString(this.__Context, this.ListId, this.ListItem, “My Field 1″) + “)”;
mySubject = Subject;
}
catch (System.Exception Ex)
{
mySubject = “ERR: Invalid Subject Line”;
}
msg.Subject = mySubject;
//STEP 5: Generate the message body
//start the handling of the message body.
msg.IsBodyHtml = true;
String myBody = Body;
//make SharePoint replace the Lookup Strings in the field
try
{
//process the Add Lookup strings [%...%] from the Workflow configuration
myBody = Helper.ProcessStringField(myBody, parent, this.__Context);
}
catch (System.Exception Ex)
{
//nothing, couldn’t process strings for some reason.
}
msg.Body = myBody;
//STEP 6: Add the file attachments as added on the list
//begin file attachments…
//set current Site information and List
try
{
//create a hierarchy of objects starting from the base SharePoint web to the List Item’s attachment collection
SPWeb spWeb = (SPWeb)(__Context.Web);
SPSite Site = (SPSite)(__Context.Site);
SPList List = spWeb.Lists[new Guid(this.ListId)];
SPListItem ListItem = List.GetItemById(this.ListItem);
//get file information from the list item attachment URL address
SPFolder spFolder = spWeb.GetFolder(ListItem.Attachments.UrlPrefix);
//iterate all files in List Item (SPFileCollection)
SPFileCollection ColOfFiles = spFolder.Files;
foreach (SPFile spFile in ColOfFiles)
{
//open a binary stream to transfer to the email message
string strFileName = spFile.Name;
System.IO.Stream binFile = spFile.OpenBinaryStream();
//If needed, add the filename to the message body also.
//msg.Body += strFileName;
//add file to email message through binary stream
msg.Attachments.Add(new Attachment(binFile, strFileName));
}
}
catch (System.Exception ex)
{
//file attachment failed.
msg.Body += “ERR: Attempted to add files: “ + ex.Message + ex.StackTrace;
}
//STEP 7: Create an object to represent the mail server and SEND the message
System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient(SMTPServerName);
//Set the credentials used to authenticate to the email server
client.UseDefaultCredentials = true;
//Send the message
client.Send(msg);
//Use the follow Catch with a Try statement to write specific errors to a local disk file for debugging issues. (Requires System.IO)
//}
//catch (System.Exception Ex)
//{
// create a writer and open the file
// TextWriter tw = new StreamWriter(”c:\\error.log”);
// write a line of text to the file
// tw.WriteLine(DateTime.Now + ” - ” + Ex.Message + Ex.StackTrace);
// close the stream
// tw.Close();
//}
}
#endregion
Compile the code; you will hopefully receive a Build Succeeded.
Please continue to Custom Workflow Actions (Part 2 of 3)
No Comments »
No comments yet.
RSS feed for comments on this post. TrackBack URL









