devtrends.com
 

Custom Workflow Actions (Part 1 of 3)

Filed under: SharePoint, Visual Studio — Tags: , — admin @ August 24, 2008 8:05 pm

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

Leave a comment

Blog Categories
  • BlackBerry (1)
  • Group Policy (1)
  • Linux (1)
  • Microsoft Office (1)
  • Networking (1)
  • Printing (3)
  • SharePoint (6)
  • SQL (1)
  • Visual Studio (3)
  • Windows XP (1)

  •