I had this nice little idea that I could teach the guys in my department how to do workflow with the Sharepoint designer so I wouldn’t have to do all that myself with the big Visual Studio Guns. All was going well until they needed to send out an email containing the URL of the site from which we were sending it. Not a link to an item in the site, the actual URL of the site itself.
“Simple!,” said I. I’ll just get it from the sharepoint object model with a custom activity. I wrote it all up, tried to use SPContext.Current.Web… and got it to puke nicely on a Null reference to Current. Of course! Why would a workflow activity have any reference at all to sharepoint? Technically a workflow activity doesn’t care where it’s hosted. But mine were to be hosted in Sharepoint, and that’s what I wanted to access. So, I looked around on the web and found nothing. I knew it had to be possible to get the context somehow, because other Sharepoint Designer Activities that came in-the-box were using it… so I looked for a clue.
I cracked open Microsoft.SharePoint.WorkflowActions.dll in Lutz Roeder’s Reflector, and lo-and behold there is a very cheap trick at work here!
Microsoft has implemented a custom dependency property by the name of __Context that is picked up as a Microsoft.SharePoint.WorkflowActions.WorkflowContext object IF it exists in your code. If you don’t implement such a property then it’s just not there, and nobody’s the wiser. This WorkflowContext object has a reference to .Site and .Web as properties, so it’s really handy to have at from within your custom activities that need to know where they are.
I’d like to post the code I found directly, but I can’t since it’s not mine. I will show you, however, what my code looks like.
public static System.Workflow.ComponentModel.DependencyProperty __ContextProperty = System.Workflow.ComponentModel.DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(FooActivity)); [Description("Context")] [ValidationOption(ValidationOption.Required)] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Microsoft.SharePoint.WorkflowActions.WorkflowContext __Context { get { return ((Microsoft.SharePoint.WorkflowActions.WorkflowContext)base.GetValue(FooActivity.__ContextProperty)); } set { base.SetValue(FooActivity.__ContextProperty, value); } }
That’s a lot of typing if you write a bunch of these activities, so I made a codesnippet just for all of you out there to have and keep forever so you don’t have to do this. (Warning, do not just make this into a base class and derive your activities from it. It will throw an exception when you try to use it. I’m not sure why, but it does. You have to add this to every single activity you create for SPD that needs to know the sharepoint context.)
Here’s the code snippet I made:
<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>spwfactivity</Title> <Shortcut>spwfactivity</Shortcut> <Description>Code snippet for creating a Sharepoint-Aware Workflow Activity</Description> <Author>Dolan</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>classname</ID> <ToolTip>Class Name</ToolTip> <Default>Activity1</Default> </Literal> </Declarations> <Code Language="csharp"><![CDATA[public partial class $classname$: System.Workflow.ComponentModel.Activity { public static System.Workflow.ComponentModel.DependencyProperty __ContextProperty = System.Workflow.ComponentModel.DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof($classname$)); [Description("Context")] [ValidationOption(ValidationOption.Required)] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Microsoft.SharePoint.WorkflowActions.WorkflowContext __Context { get { return ((Microsoft.SharePoint.WorkflowActions.WorkflowContext)base.GetValue($classname$.__ContextProperty)); } set { base.SetValue($classname$.__ContextProperty, value); } } public $classname$() { InitializeComponent(); } }]]>
You’ll also have to add a Parameter value to the .actions file with your activity that specifies the __Context property as an Input property.
. . . <Parameters> <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In"/> . . . </Parameters>
This one took me a while to figure out, but finally it worked!
If you want some more info on adding your own actions to designer (my article has thus far assumed you’re familiar with the process,) check out this link.
Update: I forgot to mention that just about the DAY after I figured this out on my own, I stumbled onto this codeproject article that dealt with this exact topic.
Just for fun I’ve also attached the snippet file so you don’t have to copy and paste it. Just save it into “%ProgramFiles%/Microsoft Visual Studio 9.0/VC#/Snippets/1033/Visual C#.”