Monday, August 9, 2010

Create custom workflow action in SharePoint 2010

There are two ways to create a workflow for SharePoint 2010, to use SharePoint Designer 2010 or to use Visual Studio 2010. To use SharePoint Designer 2010, you are under the limitation of the out of box actions that you can use. However, you are able the extend the actions limit by creating your own custom actions in Visual Studio 2010 and reuse them in SharePoint Designer 2010 in your custom workflow.
 
The out of box collect feedback workflow is very useful for some business needs and can be customized in SharePoint designer. But when it comes to the SendEmail action, SharePoint Designer doesn't support the Send From field and attachments. You’ll have to create your own custom actions to achieve these sort of requirements.
 
First, you'll need to create a new project SharePoint Activity Library under the Workflow template in Visual Studio 2010.
 
 
 
 
 
 
 
 
 
 
 
 





Rename the generated class Activity1.cs to what ever name you want, like CustomEmailAction.cs

 

Add two references: Microsoft.SharePoint and Microsoft.SharePoint.WorkflowActions
and the following two using statements:

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

 
Add the DepedencyProperties for the Activity
public static DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(string), typeof(CustomSendEmail));
public static DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(ArrayList), typeof(CustomSendEmail));
public static DependencyProperty CCProperty = DependencyProperty.Register("CC", typeof(ArrayList), typeof(CustomSendEmail));
public static DependencyProperty SubjectProperty = DependencyProperty.Register("Subject", typeof(string), typeof(CustomSendEmail));
public static DependencyProperty BodyProperty = DependencyProperty.Register("Body", typeof(string), typeof(CustomSendEmail));
public static DependencyProperty WFContextProperty = DependencyProperty.Register("WFContext", typeof(WorkflowContext), typeof(CustomSendEmail));

 
And the corresponding Properties
[Description("Sender address. If this value is not specified, default sharepoint sender address will be used")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string From
{
get
{
return ((string)(base.GetValue(CustomSendEmail.FromProperty)));
}
set
{
base.SetValue(CustomSendEmail.FromProperty, value);
}
}

[Description("Recipient address")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ArrayList To
{
get
{
return ((ArrayList)(base.GetValue(CustomSendEmail.ToProperty)));
}
set
{
base.SetValue(CustomSendEmail.ToProperty, value);
}
}

[Description("Carbon copy recipient")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ArrayList CC
{
get
{
return ((ArrayList)(base.GetValue(CustomSendEmail.CCProperty)));
}
set
{
base.SetValue(CustomSendEmail.CCProperty, value);
}
}

[Description("Subject")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Subject
{
get
{
return ((string)(base.GetValue(CustomSendEmail.SubjectProperty)));
}
set
{
base.SetValue(CustomSendEmail.SubjectProperty, value);
}
}

[Description("HTML body of the message")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Body
{
get
{
return ((string)(base.GetValue(CustomSendEmail.BodyProperty)));
}
set
{
base.SetValue(CustomSendEmail.BodyProperty, value);
}
}

[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext WFContext
{
get
{
return ((WorkflowContext)(base.GetValue(CustomSendEmail.WFContextProperty)));
}
set
{
base.SetValue(CustomSendEmail.WFContextProperty, value);
}
}
 
 
 
Add the Execute and two helper methods. The get list attachments and send email methods are not implemented which you can easily add your own.
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{

try
{
//need administrative credentials to get to Web Application Properties info
SPSecurity.RunWithElevatedPrivileges(delegate()
{

using (SPSite site = new SPSite(WFContext.Site.ID))
{
using (SPWeb web = site.AllWebs[WFContext.Web.ID])
{
string to = this.To == null ? string.Empty : ParseEmailAddress(this.WFContext, this.To);
string cc = this.CC == null ? string.Empty : ParseEmailAddress(this.WFContext, this.CC);
to = ProcessStringField(executionContext, to);
cc = ProcessStringField(executionContext, cc);
string from = ProcessStringField(executionContext, this.From);
string subject = ProcessStringField(executionContext, this.Subject);
string body = ProcessStringField(executionContext, this.Body);
SPList list=web.Lists[new Guid(this.WFContext.ListId)];
SPListItem listItem = list.Items.GetItemById(this.WFContext.ItemId);
// get the list attachments goes here
// send the email goes here
}
}
});

}
catch (Exception e)
{
throw new Exception(e.Message,e.InnerException);
}

return base.Execute(executionContext);
}

private string ProcessStringField(ActivityExecutionContext context, string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;

Activity parent = context.Activity;

while (parent.Parent != null)
{
parent = parent.Parent;
}

return Helper.ProcessStringField(str, parent, null);
}

private string ParseEmailAddress(WorkflowContext context, ArrayList curArray)
{
int num;
StringBuilder builder = new StringBuilder();
bool flag = false;
List<string> list = new List<string>();
for (num = 0; num < curArray.Count; num++)
{
if (curArray[num] == null)
{
list.Add(string.Empty);
}
else
{
string[] textArray = curArray[num].ToString().Split(new char[] { ';' });
foreach (string text in textArray)
{
list.Add(text);
}
}
}
for (num = 0; num < list.Count; num++)
{
if (list[num] != null)
{
if (!flag)
{
flag = true;
}
else
{
builder.Append(";");
}
builder.Append(Helper.ResolveToEmailName(context, list[num]));
}
}
return builder.ToString();
}


To deploy, you’ll need to create an Empty SharePoint Project.
Add a SharePoint Mapped Folder to {SharePointRoot}\Template\1033\Workflow
Add a xml file under the Workflow folder with the following content and rename it as Custom.SharePoint.Workflow.Actions. Replace the PublicKeyToken id with your id.
<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
<Actions Sequential="then" Parallel="and">
<Action Name="Custom Send an Email"
ClassName="Custom.SharePoint.Workflow.Actions.CustomSendEmail"
Assembly="Custom.SharePoint.Workflow.Actions, Version=1.0.0.0, Culture=neutral, PublicKeyToken={your id}"
Category="Custom Actions"
AppliesTo="all">
<RuleDesigner Sentence="Custom Email %1">
<FieldBind Field="To,CC,Subject,Body" Text="these users" DesignerType="Email" Id="1"/>
</RuleDesigner>
<Parameters>
<Parameter Name="From" Type="System.String, mscorlib" Direction="Optional" DesignerType="StringBuilder"
Description="Sender of the email." />
<Parameter Name="To" Type="System.Collections.ArrayList, mscorlib" Direction="In" DesignerType="Person"
Description="Recipients of the email." />
<Parameter Name="CC" Type="System.Collections.ArrayList, mscorlib" Direction="Optional" DesignerType="Person"
Description="Carbon copy recipients of the email." />
<Parameter Name="Subject" Type="System.String, mscorlib" Direction="In" DesignerType="StringBuilder"
Description="Subject line of the email." />
<Parameter Name="Body" Type="System.String, mscorlib" Direction="Optional" DesignerType="StringBuilder"
Description="Body text of the email." />
<Parameter Name="WFContext" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In" DesignerType="Hide" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>

 
Double click Package.package to open the package setting form. Click on Advanced tab at the bottom and click on the Add button to add the custom send email assembly from the project
adddll

In Web.config, add the following authorizedType under System.Workflow.ComponentModel.WorkflowCompiler

<System.Workflow.ComponentModel.WorkflowCompiler>
<authorizedTypes>
<authorizedType Assembly="Custom.SharePoint.Workflow.Actions, Version=1.0.0.0, Culture=neutral, PublicKeyToken={your id}" Namespace="Custom.SharePoint.Workflow.Actions" TypeName="*" Authorized="True" />

Now it’s ready to deploy.

No comments: