A while back I had a solution that would just not be removed. In the Solution Management screen in Central Administration, the Status for the solution was a bright red "Error". Attempting to remove it from there would give an error.
Luckily, I found a great post by Alex Thissen that clearly explains how to use stsadm to remove a malfunctioning solution. The blog post is called Remove malfunctioning Windows SharePoint Services solutions.
In a nutshell, if retracting the solution:
stsadm -o retractsolution -name solutionname.wsp -immediate
doesn't work, and if deleting the solution:
stsadm -o deletesolution -name solutionname.wsp -override
doesn't work, then
1. run an enumeration to see the scheduled deployments
stsadm -o enumdeployments
2. use the JobId to cancel the deployment
stsadm -o canceldeployment -id 2529c788-971c-46a3-b69f-a2a0a1fcc851
Afterwards, you can verify it by doing 1. again.
Showing posts with label sharepoint. Show all posts
Showing posts with label sharepoint. Show all posts
Wednesday, September 28, 2011
Thursday, September 22, 2011
How to start a SharePoint State Machine workflow without association or initiation forms
This assumes you are creating a SharePoint 2007 state machine workflow in Visual Studio.
If you ever need a workflow to start, but don't want it to have an association or initiation form, you have to do two things, both of which are in workflow.xml:
1. In the Workflow element, do not specify an AssociationUrl (for the association form) or InstantiationUrl (for the initiation form). Just leave it out of the element completely! You may want to do the same thing if you have no modification form.
The attributes indicate the web forms that will host the association and initiation forms. By default, they look like AssociationUrl="_layouts/CstWrkflIP.aspx" and InstantiationUrl="_layouts/IniWrkflIP.aspx".
2. In the MetaData element, be sure Association_FormURN and Instantiation_FormURN are commented out.
If you ever need a workflow to start, but don't want it to have an association or initiation form, you have to do two things, both of which are in workflow.xml:
1. In the Workflow element, do not specify an AssociationUrl (for the association form) or InstantiationUrl (for the initiation form). Just leave it out of the element completely! You may want to do the same thing if you have no modification form.
The attributes indicate the web forms that will host the association and initiation forms. By default, they look like AssociationUrl="_layouts/CstWrkflIP.aspx" and InstantiationUrl="_layouts/IniWrkflIP.aspx".
2. In the MetaData element, be sure Association_FormURN and Instantiation_FormURN are commented out.
Tags:
sharepoint,
state machine workflow
Wednesday, June 9, 2010
Update 12 Hive Through A Feature
As we got our SharePoint environment up and running, we needed to have the ability to make changes to our 12-Hive. We also wanted to have the flexibility to undo the changes and restore the 12-Hive to the way it was originally.
This post discusses the scenario where you need to make changes to files that already exist in the 12-Hive. I call this solution HiveUpdates. If you have a need to add new files to the 12-Hive that do not already exist, such as new images or layouts, check out my other article Add New Files To 12-Hive Through A Solution. I call that solution HiveAdditions. We decided it would be best for us to separate these two types of Hive changes into two separate solutions.
This solution, HiveUpdates, allows us to modify existing Hive files. This solution contains a feature that, when activated, makes a backup copy of the original file, then overwrites the original with the new file. When the feature is deactivated, it copies the backup file back to the original filename.
The method I used is based on the article 12 Hive System-File Changes: One Feature to rule them all!. The above post has a routine that is called when the feature is activated or when its deactivated. The code that fires simply "swaps" newly changed 12-hive files with their corresponding original 12-hive files. This happens both when the feature is activated or deactivated. While the "swap" code seems like a good idea on the surface (the same code that deploys the changes will swap files back to undeploy them), I found in practice this seemed risky and even prone to error if something went wrong on the feature activate or feature deactivate. I sometimes got the files out of sync and would lose the original 12-Hive versions.
Therefore, I modified the code to first make a backup copy of a 12-Hive file I want to update, then overwrite the original 12-hive file with my changed version (this is an activate). On a deactivate, it overwrites my changed version with the original's backup file. The original's backup file always stays there as a backup, which makes me feel very safe. And I like feeling safe. I like my job and want to keep it.
1. In Visual Studio 2008, File -> New -> Project
2. Under Visual C#, pick SharePoint, then Empty
3. For Name, type: HiveUpdates
4. For Location, type: D:\development
5. Click OK
6. Select Full Trust (Deploy to GAC) and click OK
7. In WSP View (View -> Other Windows -> WSP View), click "Create new feature"
8. Change Feature Scope to Farm
9. Check Add feature receiver
10. click OK
11. In Solution Explorer, rename Feature1 to HiveUpdates
12. Rename Feature1Receiver.cs to HiveUpdatesReceiver.cs
13. Double-click HiveUpdatesReceiver.cs to open it
14. Rename class from FeatureReceiver1 to HiveUpdatesReceiver
15. Below that, you'll see the constructor "public FeatureReceiver1". Rename constructor from FeatureReceiver1 to HiveUpdatesReceiver.
16. In WSP View, hit refresh and notice FeatureReceiver1 is now HiveUpdatesReceiver
17. Rename Feature1 folder to HiveUpdates
Make the following code modifications/additions to HiveUpdatesReceiver.cs:
18. At the top of the code, add the following using statements:
19. Above the HiveUpdatesReceiver constructor, add the following:
20. In the FeatureActivated method, add:
21. In the FeatureDeactivating method, add:
22. Below the FeatureUninstalling method, add the following 4 methods (DeployToHive, RemoveFromHive, GetHiveFile, GetLocalDirectory):
Now we need to create the appropriate 12-Hive file structure for the feature in Solution Explorer. Any 12-Hive files we want to update will have their newer versions placed in the file structure we are about to create.
23. In Solution Explorer, right-click project HiveUpdates, then click Add -> New Item... -> SharePoint -> Template.
24. You will notice a folder called Templates was created in the Solution Explorer. Delete the default file TemplateFile1.txt.
25. Under Templates, create a folder called FEATURES.
26. Under FEATURES, create a folder called HiveUpdates.
So far, we have created 12/TEMPLATE/FEATURES/HiveUpdates. This is the location in the 12-Hive where that feature's feature.xml gets deployed to. But now, we must create another 12-Hive structure under this folder. This is where our newer files will be deployed to when the solution is initially deployed. When the feature is activated, this is where the above code will store backups of the original files from the 12-Hive, as well as the source from which to overwrite the 12-Hive files with.
27. Under HiveUpdates, create a folder called 12.
28. Under 12, create a folder called TEMPLATE.
29. Under TEMPLATE, create a folder called LAYOUTS.
30. Copy application.master in here. Either make some small change to it or at the very least change the date on the file so its different from what it is in the 12-Hive. Be sure to right-click application.master and click Include In Project if it is not already included.
31. In WSP View, double-click feature.xml.
32. Change the value of Title from Feature1 to HiveUpdates.
33. Verify Scope is set to Farm.
33. After the title or scope, add: ActivateOnDefault="false"
34. Verify your manifest was created correctly. In WSP View, Under HiveUpdates, you should see manifest.xml. Double-click manifest.xml and verify your file path for application.master is FEATURES\HiveUpdates\12\TEMPLATE\LAYOUTS.
35. click Build -> Rebuild Solution
36. click Build -> Package Solution
The solution package that you just created in the above step, HiveUpdates.wsp, was created in your project's bin\debug folder.
To deploy the solution:
37. create folder d:\deploy
38. copy HiveUpdates.wsp from project's bin\debug folder to d:\deploy
39. In d:\deploy, create two batch files, add_hiveupdates.bat and remove_hiveupdates.bat.
add_hiveupdates.bat:
remove_hiveupdates.bat:
40. in d:\deploy, type add_hiveupdates.wsp and hit enter
41. verify solution was deployed in Central Administration -> Operations -> Solution Management
42. verify files were added to 12-Hive!
This post discusses the scenario where you need to make changes to files that already exist in the 12-Hive. I call this solution HiveUpdates. If you have a need to add new files to the 12-Hive that do not already exist, such as new images or layouts, check out my other article Add New Files To 12-Hive Through A Solution. I call that solution HiveAdditions. We decided it would be best for us to separate these two types of Hive changes into two separate solutions.
This solution, HiveUpdates, allows us to modify existing Hive files. This solution contains a feature that, when activated, makes a backup copy of the original file, then overwrites the original with the new file. When the feature is deactivated, it copies the backup file back to the original filename.
The method I used is based on the article 12 Hive System-File Changes: One Feature to rule them all!. The above post has a routine that is called when the feature is activated or when its deactivated. The code that fires simply "swaps" newly changed 12-hive files with their corresponding original 12-hive files. This happens both when the feature is activated or deactivated. While the "swap" code seems like a good idea on the surface (the same code that deploys the changes will swap files back to undeploy them), I found in practice this seemed risky and even prone to error if something went wrong on the feature activate or feature deactivate. I sometimes got the files out of sync and would lose the original 12-Hive versions.
Therefore, I modified the code to first make a backup copy of a 12-Hive file I want to update, then overwrite the original 12-hive file with my changed version (this is an activate). On a deactivate, it overwrites my changed version with the original's backup file. The original's backup file always stays there as a backup, which makes me feel very safe. And I like feeling safe. I like my job and want to keep it.
1. In Visual Studio 2008, File -> New -> Project
2. Under Visual C#, pick SharePoint, then Empty
3. For Name, type: HiveUpdates
4. For Location, type: D:\development
5. Click OK
6. Select Full Trust (Deploy to GAC) and click OK
7. In WSP View (View -> Other Windows -> WSP View), click "Create new feature"
8. Change Feature Scope to Farm
9. Check Add feature receiver
10. click OK
11. In Solution Explorer, rename Feature1 to HiveUpdates
12. Rename Feature1Receiver.cs to HiveUpdatesReceiver.cs
13. Double-click HiveUpdatesReceiver.cs to open it
14. Rename class from FeatureReceiver1 to HiveUpdatesReceiver
15. Below that, you'll see the constructor "public FeatureReceiver1". Rename constructor from FeatureReceiver1 to HiveUpdatesReceiver.
16. In WSP View, hit refresh and notice FeatureReceiver1 is now HiveUpdatesReceiver
17. Rename Feature1 folder to HiveUpdates
Make the following code modifications/additions to HiveUpdatesReceiver.cs:
18. At the top of the code, add the following using statements:
using Microsoft.SharePoint.Administration;
using System.IO;
using System.IO;
19. Above the HiveUpdatesReceiver constructor, add the following:
private DirectoryInfo localDirectory = new DirectoryInfo(@"C:\");
20. In the FeatureActivated method, add:
DeployToHive(properties);
21. In the FeatureDeactivating method, add:
RemoveFromHive(properties);
22. Below the FeatureUninstalling method, add the following 4 methods (DeployToHive, RemoveFromHive, GetHiveFile, GetLocalDirectory):
private void DeployToHive(SPFeatureReceiverProperties properties)
{
//loop through each server in the farm
foreach (SPServer server in properties.Definition.Farm.Servers)
{
//if the current server is a WebFrontEnd server
if (server.Role == SPServerRole.WebFrontEnd || server.Role == SPServerRole.Application
|| server.Role == SPServerRole.SingleServer)
{
string localFilePath = GetLocalDirectory(properties.Definition.RootDirectory,
server.Name);
localDirectory = new DirectoryInfo(localFilePath);
foreach (FileInfo fileinfo in localDirectory.GetFiles("*",
SearchOption.AllDirectories))
{
//if current file in feature exists in 12 hive on current server
if (File.Exists(GetHiveFile(fileinfo.FullName, server.Name))
&& (!fileinfo.FullName.Contains(".hivebackup")))
// backup file from 12 hive to feature
File.Copy(GetHiveFile(fileinfo.FullName, server.Name),
fileinfo.FullName + ".hivebackup", true);
//if current file in feature exists
if (File.Exists(fileinfo.FullName) &&
(!fileinfo.FullName.Contains(".hivebackup")))
// copy local file to the 12 hive
File.Copy(fileinfo.FullName, GetHiveFile(fileinfo.FullName, server.Name),
true);
}
}
}
}
private void RemoveFromHive(SPFeatureReceiverProperties properties)
{
//loop through each server in the farm
foreach (SPServer server in properties.Definition.Farm.Servers)
{
//if the current server is a WebFrontEnd server
if (server.Role == SPServerRole.WebFrontEnd
|| server.Role == SPServerRole.Application
|| server.Role == SPServerRole.SingleServer)
{
string localFilePath = GetLocalDirectory(properties.Definition.RootDirectory,
server.Name);
localDirectory = new DirectoryInfo(localFilePath);
foreach (FileInfo fileinfo in localDirectory.GetFiles("*",
SearchOption.AllDirectories))
{
//if current backup file in feature exists in 12 hive
if (File.Exists(fileinfo.FullName + ".hivebackup"))
// copy hivebackup file back to the 12 hive
File.Copy(fileinfo.FullName + ".hivebackup",
GetHiveFile(fileinfo.FullName, server.Name), true);
}
}
}
}
string GetHiveFile(string fileName, string serverName)
{
if (fileName.StartsWith(localDirectory.FullName))
{ // file is local
string p = @"\\" + serverName
+ @"\C$\Program Files\Common Files\Microsoft Shared\web server extensions\12"
+ fileName.Remove(0, localDirectory.FullName.Length);
return p;
}
throw new Exception("Filepath doesn't point to correct local hive!");
}
string GetLocalDirectory(string filePath, string serverName)
{
return @"\\" + serverName + @"\" + @filePath.Replace(":", "$") + @"\12";
//write a class that writes exceptions to the event log
throw new Exception("Filepath doesn't point to correct local hive!");
}
Now we need to create the appropriate 12-Hive file structure for the feature in Solution Explorer. Any 12-Hive files we want to update will have their newer versions placed in the file structure we are about to create.
23. In Solution Explorer, right-click project HiveUpdates, then click Add -> New Item... -> SharePoint -> Template.
24. You will notice a folder called Templates was created in the Solution Explorer. Delete the default file TemplateFile1.txt.
25. Under Templates, create a folder called FEATURES.
26. Under FEATURES, create a folder called HiveUpdates.
So far, we have created 12/TEMPLATE/FEATURES/HiveUpdates. This is the location in the 12-Hive where that feature's feature.xml gets deployed to. But now, we must create another 12-Hive structure under this folder. This is where our newer files will be deployed to when the solution is initially deployed. When the feature is activated, this is where the above code will store backups of the original files from the 12-Hive, as well as the source from which to overwrite the 12-Hive files with.
27. Under HiveUpdates, create a folder called 12.
28. Under 12, create a folder called TEMPLATE.
29. Under TEMPLATE, create a folder called LAYOUTS.
30. Copy application.master in here. Either make some small change to it or at the very least change the date on the file so its different from what it is in the 12-Hive. Be sure to right-click application.master and click Include In Project if it is not already included.
31. In WSP View, double-click feature.xml.
32. Change the value of Title from Feature1 to HiveUpdates.
33. Verify Scope is set to Farm.
33. After the title or scope, add: ActivateOnDefault="false"
34. Verify your manifest was created correctly. In WSP View, Under HiveUpdates, you should see manifest.xml. Double-click manifest.xml and verify your file path for application.master is FEATURES\HiveUpdates\12\TEMPLATE\LAYOUTS.
35. click Build -> Rebuild Solution
36. click Build -> Package Solution
The solution package that you just created in the above step, HiveUpdates.wsp, was created in your project's bin\debug folder.
To deploy the solution:
37. create folder d:\deploy
38. copy HiveUpdates.wsp from project's bin\debug folder to d:\deploy
39. In d:\deploy, create two batch files, add_hiveupdates.bat and remove_hiveupdates.bat.
add_hiveupdates.bat:
c:
cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN
stsadm -o addsolution -filename d:\deploy\HiveUpdates\HiveUpdates.wsp
stsadm -o deploysolution -name HiveUpdates.wsp -immediate -allowGacDeployment
pause
stsadm -o activatefeature -name HiveUpdates
d:
cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN
stsadm -o addsolution -filename d:\deploy\HiveUpdates\HiveUpdates.wsp
stsadm -o deploysolution -name HiveUpdates.wsp -immediate -allowGacDeployment
pause
stsadm -o activatefeature -name HiveUpdates
d:
remove_hiveupdates.bat:
c:
cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN
stsadm -o deactivatefeature -name HiveUpdates
stsadm -o retractsolution -name HiveUpdates.wsp -immediate
pause
stsadm -o deletesolution -name HiveUpdates.wsp
d:
cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN
stsadm -o deactivatefeature -name HiveUpdates
stsadm -o retractsolution -name HiveUpdates.wsp -immediate
pause
stsadm -o deletesolution -name HiveUpdates.wsp
d:
40. in d:\deploy, type add_hiveupdates.wsp and hit enter
41. verify solution was deployed in Central Administration -> Operations -> Solution Management
42. verify files were added to 12-Hive!
Tags:
feature,
sharepoint,
solution,
update 12-hive
Thursday, August 20, 2009
How to create a SharePoint State Machine Workflow: Part 6 - Add task notification emails
Here in Part 6, we'll add email notification events so when a task is assigned, approved, or rejected, the appropriate emails are sent to the proper people. This part assumes you have completed part 4.
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
1. go back to the design surface and double-click PeerReviewerApprovalInitialization to drill down into it
2. drag and drop a SendEmail activity below hlogPeerReviewerApprovalTaskCreated
and set the following properties:
a. Name: sendPeerReviewerApprovalTaskEmail
b. Body: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
c. CC: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
d. CorrelationToken: choose workflowToken from dropdownlist
e. Subject: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
f. To: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
g. MethodInvoking: type PeerReviewerApprovalTaskEmail and hit enter
h. You will be taken to the code stub for PeerReviewerApprovalTaskEmail. Copy and paste the following code:
'get PeerReviewer's email address
Dim PeerReviewerObject As SPUser = GetUserObject(PeerReviewer)
Dim PeerReviewerEmail = PeerReviewerObject.Email
'get CC's email address
Dim CCObject As SPUser = GetUserObject(CC)
Dim CCEmail = CCObject.Email
sendPeerReviewerApprovalTaskEmail_To = PeerReviewerEmail
sendPeerReviewerApprovalTaskEmail_CC = CCEmail
sendPeerReviewerApprovalTaskEmail_Subject = "Approval of " & workflowProperties.Item.File.Name & " has been assigned to you."
sendPeerReviewerApprovalTaskEmail_Body = "<span style='font-family: arial; font-size: medium'>" & _
"Task assigned by " & workflowProperties.OriginatorUser.Name & " on " & DateTime.Now & _
"</span>" & _
"<br>" & _
"<span style='font-family: arial; font-size: x-small'>" & _
"Due by " & DateTime.Today & _
"<br><br>" & _
"Instructions: <br>" & Instructions & _
"<br><br>" & _
"Please approve " & workflowProperties.Item.File.Name & _
"<br><br>" & _
"To complete this task:" & _
"<br><br>" & _
" 1. Review " & "<a href='" & workflowProperties.SiteUrl & "\" & workflowProperties.ItemUrl & "'>" & workflowProperties.Item.File.Name & "</a>" & "." & _
"<br>" & _
" 2. Use the <b>Edit this task</b> button to Approve/Reject the document." & _
"<br><br>" & _
"To view this workflow's history, click " & "<a href=" & workflowProperties.SiteUrl & "/_layouts/WrkStat.aspx?List=" & workflowProperties.ListId.ToString & "&WorkflowInstanceID=" & workflowProperties.WorkflowId.ToString & ">here</a>" & "." & _
"</span>"
'if this is the first time we have gone to the peer reviewer state, do not show comments
' in email
If ArrivedFromInitiatorState = True Then
'get the comments entered by the initiator
Dim Comments As String = onInitiatorApprovalChanged.AfterProperties.ExtendedProperties("txtComments").ToString
sendPeerReviewerApprovalTaskEmail_Body = sendPeerReviewerApprovalTaskEmail_Body & _
"<span style='font-family: arial; font-size: x-small'>" & _
"<br><br>" & _
"<font color=red>Comments:</font> <br>" & Comments & _
"</span>"
End If
3. go back to the design surface and double-click InitiatorApprovalInitialization to drill down into it
4. drag and drop a SendEmail activity below hlogPeerReviewerApprovalTaskCreated
and set the following properties:
a. Name: sendInitiatorApprovalTaskEmail
b. Body: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
c. CC: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
d. CorrelationToken: choose workflowToken from dropdownlist
e. Subject: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
f. To: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
g. MethodInvoking: type InitiatorApprovalTaskEmail and hit enter
h. You will be taken to the code stub for PeerReviewerApprovalTaskEmail. Copy and paste the following code:
'get Initiator's email address
Dim InitiatorObject As SPUser = GetUserObject(workflowProperties.Originator)
Dim InitiatorEmail = InitiatorObject.Email
'get CC's email address
Dim CCObject As SPUser = GetUserObject(CC)
Dim CCEmail = CCObject.Email
'get PeerReviewer's name
Dim PeerReviewerObject As SPUser = GetUserObject(PeerReviewer)
Dim PeerReviewerName = PeerReviewerObject.Name
'get the comments entered by the peer reviewer
Dim Comments As String = onPeerReviewerApprovalChanged.AfterProperties.ExtendedProperties("txtComments").ToString
sendInitiatorApprovalTaskEmail_To = InitiatorEmail
sendInitiatorApprovalTaskEmail_CC = CCEmail
sendInitiatorApprovalTaskEmail_Subject = "Approval of " & workflowProperties.Item.File.Name & " has been assigned to you."
sendInitiatorApprovalTaskEmail_Body = "<span style='font-family: arial; font-size: medium'>" & _
"Task assigned by " & PeerReviewerName & " on " & DateTime.Now & _
"</span>" & _
"<br>" & _
"<span style='font-family: arial; font-size: x-small'>" & _
"Due by " & DateTime.Today & _
"<br><br>" & _
"Instructions: <br>" & Instructions & _
"<br><br>" & _
"Please approve " & workflowProperties.Item.File.Name & _
"<br><br>" & _
"To complete this task:" & _
"<br><br>" & _
" 1. Review " & "<a href='" & workflowProperties.SiteUrl & "\" & workflowProperties.ItemUrl & "'>" & workflowProperties.Item.File.Name & "</a>" & "." & _
"<br>" & _
" 2. Use the <b>Edit this task</b> button to Approve/Reject the document." & _
"<br><br>" & _
"To view this workflow's history, click " & "<a href=" & workflowProperties.SiteUrl & "/_layouts/WrkStat.aspx?List=" & workflowProperties.ListId.ToString & "&WorkflowInstanceID=" & workflowProperties.WorkflowId.ToString & ">here</a>" & "." & _
"<br><br>" & _
"<font color=red>Comments:</font> <br>" & Comments & _
"</span>"
5. go back to the design surface and double-click PeerReviewerApprovalActivities to drill down into it
6. drag and drop a SendEmail activity between hlogPeerReviewerApproved and hlogWorkflowCompleted
and set the following properties:
a. Name: sendPeerReviewerApprovedEmail
b. Body: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
c. CC: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
d. CorrelationToken: choose workflowToken from dropdownlist
e. Subject: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
f. To: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
g. MethodInvoking: type InitiatorApprovalTaskEmail and hit enter
h. You will be taken to the code stub for PeerReviewerApprovedEmail. Copy and paste the following code:
'get Initiator's email address
Dim InitiatorObject As SPUser = GetUserObject(workflowProperties.Originator)
Dim InitiatorEmail = InitiatorObject.Email
'get CC's email address
Dim CCObject As SPUser = GetUserObject(CC)
Dim CCEmail = CCObject.Email
'get PeerReviewer's name
Dim PeerReviewerObject As SPUser = GetUserObject(PeerReviewer)
Dim PeerReviewerName = PeerReviewerObject.Name
sendPeerReviewerApprovedEmail_To = InitiatorEmail
sendPeerReviewerApprovedEmail_CC = CCEmail
sendPeerReviewerApprovedEmail_Subject = workflowProperties.Item.File.Name & " has been approved by " & PeerReviewerName & ". Peer Review Complete."
sendPeerReviewerApprovedEmail_Body = "<span style='font-family: arial; font-size: medium'>" & _
workflowProperties.Item.File.Name & " was approved by " & PeerReviewerName & " on " & DateTime.Now & _
"<br><br>" & _
"Peer Review Complete" & _
"<br><br>" & _
"<span style='font-family: arial; font-size: x-small'>" & _
"To view this workflow's history, click " & "<a href=" & workflowProperties.SiteUrl & "/_layouts/WrkStat.aspx?List=" & workflowProperties.ListId.ToString & "&WorkflowInstanceID=" & workflowProperties.WorkflowId.ToString & ">here</a>" & "." & _
"</span></span>"
7. drag and drop a SendEmail activity between hlogPeerReviewerRejected and setStateInitiatorApproval
and set the following properties:
a. Name: sendPeerReviewerRejectedEmail
b. Body: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
c. CC: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
d. CorrelationToken: choose workflowToken from dropdownlist
e. Subject: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
f. To: click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
g. MethodInvoking: type InitiatorApprovalTaskEmail and hit enter
h. You will be taken to the code stub for PeerReviewerRejectedEmail. Copy and paste the following code:
'get Initiator's email address
Dim InitiatorObject As SPUser = GetUserObject(workflowProperties.Originator)
Dim InitiatorEmail = InitiatorObject.Email
'get CC's email address
Dim CCObject As SPUser = GetUserObject(CC)
Dim CCEmail = CCObject.Email
'get PeerReviewer's name
Dim PeerReviewerObject As SPUser = GetUserObject(PeerReviewer)
Dim PeerReviewerName = PeerReviewerObject.Name
sendPeerReviewerRejectedEmail_To = InitiatorEmail
sendPeerReviewerRejectedEmail_CC = CCEmail
sendPeerReviewerRejectedEmail_Subject = workflowProperties.Item.File.Name & " has been rejected by " & PeerReviewerName
sendPeerReviewerRejectedEmail_Body = "<span style='font-family: arial; font-size: medium'>" & _
workflowProperties.Item.File.Name & " was rejected by " & PeerReviewerName & " on " & DateTime.Now & _
"<br><br>" & _
"<span style='font-family: arial; font-size: x-small'>" & _
"To view this workflow's history, click " & "<a href=" & workflowProperties.SiteUrl & "/_layouts/WrkStat.aspx?List=" & workflowProperties.ListId.ToString & "&WorkflowInstanceID=" & workflowProperties.WorkflowId.ToString & ">here</a>" & "." & _
"</span></span>"
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
Tags:
sharepoint,
state machine workflow
How to create a SharePoint State Machine Workflow: Part 5 - Add workflow history logging
Here in Part 5, We'll add history logging to the workflow. This is really useful as it allows the user to see messages about the status of the workflow, such as who approved it or rejected it. This part assumes you have completed part 4.
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
1. add this function to the code:
Private Function GetUserObject(ByVal accountID As String) As SPUser
If accountID.IndexOf("\") > 0 Then
Dim user As SPUser = Me.workflowProperties.Web.SiteUsers(accountID)
Return user
Else
Dim user As SPUser = Me.workflowProperties.Web.SiteUsers("OSS\" + accountID)
Return user
End If
End Function
2. go back to the design surface and double-click InitialStateActivities to drill down into it
3. drag and drop a LogToHistoryListActivity activity between onWorkflowActivated and setStatePeerReviewerApproval1
and set the following properties:
a. name: hlogWorkflowStarted
b. EventId: WorkflowStarted
c. HistoryDescription: Workflow started.
d. HistoryOutcome: create property hlogWorkflowStarted_HistoryOutcome
e. UserId: create property hlogWorkflowStarted_UserId
4. double-click the onWorkflowActivated activity to be taken to its code stub
5. copy and paste the following code to have 1. the workflow's originator assigned to the UserId property of the
logToHistory activity, and 2. the instructions entered by the originator assigned to the HistoryOutcome property:
'assign workflow's executor to the LogToHistory activity's user id
Dim executor As SPUser = GetUserObject(workflowProperties.Originator)
hlogWorkflowStarted_UserId = executor.ID
'assign the instructions to the HistoryOutcome property
hlogWorkflowStarted_HistoryOutcome = "Instructions: " & initform.txtInstructions
6. go back to the design surface and double-click InitiatorApprovalInitialization to drill down into it
7. drag and drop a LogToHistoryListActivity activity below createInitiatorApprovalTask
and set the following properties:
a. name: hlogInitiatorApprovalTaskCreated
b. EventId: TaskCreated
c. HistoryDescription: Initiator approval task created. Awaiting approval.
d. UserId: 0
8. go back to the design surface and double-click InitiatorApprovalActivities to drill down into it
9. drag and drop a LogToHistoryListActivity activity between ifInitiatorApproved and setStatePeerReviewerApproval
and set the following properties:
a. name: hlogInitiatorApproved
b. EventId: TaskCompleted
c. HistoryDescription: Initiator approved.
d. HistoryOutcome: create property hlogInitiatorApproved_HistoryOutcome
e. UserId: create property hlogInitiatorApproved_UserId
10. click on the onInitiatorApprovalChanged activity and set the following property
a. Executor: create property onInitiatorApprovalChanged_Executor
11. double-click onInitiatorApprovalChanged activity to be taken to its code stub
12. copy and paste the following code to have the task's executor assigned to the UserId property of the
logToHistory activity:
'assign task's executor to the LogToHistory activity's user id
Dim executor As SPUser = GetUserObject(onInitiatorApprovalChanged_Executor)
hlogInitiatorApproved_UserId = executor.ID
'assign the task's comments to the HistoryOutcome property
hlogInitiatorApproved_HistoryOutcome = "Comments: " & onInitiatorApprovalChanged.AfterProperties.ExtendedProperties("txtComments").ToString
13. go back to the design surface and double-click PeerReviewerApprovalInitialization to drill down into it
14. drag and drop a LogToHistoryListActivity activity below createPeerReviewerApprovalTask
and set the following properties:
a. name: hlogPeerReviewerApprovalTaskCreated
b. EventId: TaskCreated
c. HistoryDescription: Peer Reviewer approval task created. Awaiting approval.
d. UserId: 0
15. go back to the design surface and double-click PeerReviewerApprovalActivities to drill down into it
16. drag and drop a LogToHistoryListActivity activity between ifPeerReviewerApproved and setStateFinalState
and set the following properties:
a. name: hlogPeerReviewerApproved
b. EventId: TaskCompleted
c. HistoryDescription: Peer Reviewer approved.
d. HistoryOutcome: create property hlogPeerReviewerApproved_HistoryOutcome
e. UserId: create property hlogPeerReviewerApproved_UserId
17. drag and drop a LogToHistoryListActivity activity between ifPeerReviewerRejected and setStateInitiatorApproval
and set the following properties:
a. name: hlogPeerReviewerRejected
b. EventId: TaskCompleted
c. HistoryDescription: Peer Reviewer rejected.
d. HistoryOutcome: create property hlogPeerReviewerRejected_HistoryOutcome
e. UserId: create property hlogPeerReviewerRejected_UserId
18. click on the onPeerReviewerApprovalChanged activity and set the following property
a. Executor: create property onPeerReviewerApprovalChanged_Executor
19. double-click onPeerReviewerApprovalChanged activity to be taken to its code stub
20. copy and paste the following code to have the task's executor assigned to the UserId property of the
logToHistory activity:
'assign task's executor to the LogToHistory activity's user id
Dim executor As SPUser = GetUserObject(onPeerReviewerApprovalChanged_Executor)
hlogPeerReviewerApproved_UserId = executor.ID
hlogPeerReviewerRejected_UserId = executor.ID
'assign the task's comments to the HistoryOutcome property
hlogPeerReviewerApproved_HistoryOutcome = "Comments: " & onPeerReviewerApprovalChanged.AfterProperties.ExtendedProperties("txtComments").ToString
hlogPeerReviewerRejected_HistoryOutcome = "Comments: " & onPeerReviewerApprovalChanged.AfterProperties.ExtendedProperties("txtComments").ToString
21. go back to the design surface and double-click PeerReviewerApprovalActivities to drill down into it
22. drag and drop a LogToHistoryListActivity activity between hlogPeerReviewerApproved and setStateFinalState
and set the following properties:
a. name: hlogWorkflowCompleted
b. EventId: WorkflowCompleted
c. HistoryDescription: Workflow completed.
d. UserId: 0
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
Tags:
sharepoint,
state machine workflow
Wednesday, August 19, 2009
How to create a SharePoint State Machine Workflow: Part 4 - Create the state machine workflow
Here in Part 4, we create the workflow and deploy it to the SharePoint server. I have done these steps with both Visual Studio 2005 and 2008. When deploying, it tells you what to do based on whether you are using 2005 or 2008. Also, I included VB as well as C# code depending on your tastes. It seems like most everybody is using C#. We use VB in-house traditionally so I figured I would stay with VB. You'll notice that there really isn't a whole lot of code here at all - just lots and lots of steps. This workflow works off of four states: InitialState, InitiatorApproval, PeerReviewerApproval and FinalState.
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
1. In Visual Studio 2005, File -> New -> Project -> SharePoint -> SharePoint Server State Machine, call it AgendaItemPeerReview
In Visual Studio 2008, File -> New -> Project -> Visual Basic -> Office -> 2007 -> SharePoint 2007 State Machine Workflow,
call it AgendaItemPeerReview
set location to d:\asp or whatever you want
Rename Workflow1.vb to PeerReviewWorkflow.vb and doubleclick it to open the design surface.
It will start out with a state called Workflow1InitialState. Rename Workflow1InitialState to InitialState.
Note: The workflow MUST have a different name than the solution/project. Do NOT rename file workflow1.cs (and thus the
workflow1 class) to the same name you chose as the solution/project.
2. add three more states and name them InitiatorApproval, PeerReviewerApproval and FinalState
3. right click workflow and set CompletedStateFinish property to the new state you created called FinalState
be sure InitialStateName is InitialState
We will build the workflow by adding activities to the various states.
Afterwards, we will come back and add the necessary code. I do it this way because some code is autogenerated while
other code must be written by us, and by building the whole workflow first, all the generated code will be together,
then all our code will be together below the generated code. Also, this provides more of a top-down approach to
designing (and understanding) the workflow.
4. in state InitialState, click eventDrivenActivity1 and rename it to InitialStateActivities
5. double-click this activity to drill down into it; in here we will create a sequential workflow; notice there is
already an onWorkflowActivated1 event
a. Rename onWorkflowActivated1 to onWorkflowActivated.
b. Drill down into CorrelationToken and change OwnerActivityName to PeerReviewWorkflow.
c. Drill down into WorkflowProperties and change Name to PeerReviewWorkflow.
6. in the Toolbox, find the Windows Workflow section and drag and drop a SetState event below onWorkflowActivated
a. rename setState activity to setStatePeerReviewerApproval
b. set TargetStateName property to PeerReviewerApproval
c. click on PeerReviewWorkflow to go back to workflow view; notice the line drawn from InitialState to
state PeerReviewerApproval
For the two approval states, we are going to add the same stuff, ie initialization code for when a state is entered,
code to execute while in the state, and code to execute when the state is completed.
7. for each of the two states, drag and drop into them:
a. a StateInitialization activity
b. an EventDriven activity
c. a StateFinalization activity
8. in stateInitiatorApproval, rename the activities to:
InitiatorApprovalInitialization
InitiatorApprovalActivities
InitiatorApprovalFinalization
9. in statePeerReviewerApproval, rename the activities to:
PeerReviewerApprovalInitialization
PeerReviewerApprovalActivities
PeerReviewerApprovalFinalization
We will now add the specific tasks within each of these three activities (initialization, activities, finalization) for each state.
Essentially, we will be creating sequential workflows within each of these activities.
10. in InitiatorApproval, double-click InitiatorApprovalInitialization to drill down into it
11. drag and drop a CreateTask activity and set the following properties:
a. rename it createInitiatorApprovalTask
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, click the "Bind to a new member" tab, remove the 1 from the end of the "New member name"
and click OK
d. follow the same step for TaskProperties
e. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface
12. double-click InitiatorApprovalFinalization to drill down into it
13. drag and drop a CompleteTask activity and set the following properties:
a. rename it completeInitiatorApprovalTask
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createInitiatorApprovalTask_TaskId
d. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface
14. double-click InitiatorApprovalActivities to drill down into it
15. drag and drop an OnTaskChanged activity and set the following properties:
a. rename it onInitiatorApprovalChanged
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createInitiatorApprovalTask_TaskId
d. for AfterProperties and BeforeProperties, click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
16. drag and drop an IfElse activity and rename it InitiatorApprovalStatus
17. click on the left branch and set the following properties:
a. rename it ifInitiatorApproved
b. for Condition, click Code Condition
18. drag and drop a setState activity under ifInitiatorApproved
a. rename it setStatePeerReviewerApproval
b. set the TargetStateName to PeerReviewerApproval
19. click PeerReviewWorkflow to go back to higher-level view of workflow
20. in PeerReviewerApproval, double-click PeerReviewerApprovalInitialization to drill down into it
21. drag and drop a CreateTask activity and set the following properties:
a. rename it createPeerReviewerApprovalTask
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
PeerReviewerApproval
c. for TaskId, click the ellipses, click the "Bind to a new member" tab, remove the 1 from the end of the "New member name"
and click OK
d. follow the same step for TaskProperties
e. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface
22. double-click PeerReviewerApprovalFinalization to drill down into it
23. drag and drop a CompleteTask activity and set the following properties:
a. rename it completePeerReviewerApprovalTask
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
PeerReviewerApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createPeerReviewerApprovalTask_TaskId
d. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface
24. double-click PeerReviewerApprovalActivities to drill down into it
25. drag and drop an OnTaskChanged activity and set the following properties:
a. rename it onPeerReviewerApprovalChanged
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createPeerReviewerApprovalTask_TaskId
d. for AfterProperties and BeforeProperties, click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK
26. drag and drop an IfElse activity and rename it PeerReviewerApprovalStatus
27. click on the left branch and set the following properties:
a. rename it ifPeerReviewerApproved
b. for Condition, click Code Condition
28. drag and drop a setState activity under ifPeerReviewerApproved
a. rename it setStateFinalState
b. set the TargetStateName to FinalState
29. click on the right branch and set the following properties:
a. rename it ifPeerReviewerRejected
b. for Condition, click Code Condition
30. drag and drop a setState activity under ifPeerReviewerRejected
a. rename it setStateInitiatorApproval
b. set the TargetStateName to InitiatorApproval
Now we are ready to start adding code. First we will add code in InitialState, for when the workflow starts.
31. Go to the code-behind and find the following line (this is the VB version, but the C# version looks almost identical):
Public workflowProperties As SPWorkflowActivationProperties = New Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties
Above it, type:
VISUAL BASIC
------------
Public workflowId As System.Guid
VISUAL C#
---------
public Guid workflowId = default(System.Guid);
Below the workflowProperties line, type:
VISUAL BASIC
------------
Dim PeerReviewer As String
Dim CC As String
Dim Instructions As String
VISUAL C#
---------
private String PeerReviewer = default(String);
private String CC = default(String);
private String Instructions = default(String);
32. back on design surface, double-click InitialStateActivitites to drill down into it
33. double-click onWorkflowActivated. This will take you to the code behind and show the generated code stub
onWorkflowActivated_Invoked. Copy and paste the following code into it:
VISUAL BASIC
------------
workflowId = workflowProperties.WorkflowId
Dim serializer As XmlSerializer = New XmlSerializer(GetType(InitForm))
Dim reader As XmlTextReader = New XmlTextReader(New System.IO.StringReader(workflowProperties.InitiationData))
Dim initform As InitForm = CType(serializer.Deserialize(reader), InitForm)
Dim prPeerReviewer() As Person
prPeerReviewer = initform.gpPeerReviewer
PeerReviewer = prPeerReviewer(0).AccountId
Dim prCC() As Person
prCC = initform.gpCC
CC = prCC(0).AccountId
Instructions = initform.txtInstructions
VISUAL C#
---------
workflowId = workflowProperties.WorkflowId;
XmlSerializer serializer = new XmlSerializer(typeof(InitForm));
XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(workflowProperties.InitiationData));
InitForm initform = (InitForm)serializer.Deserialize(reader);
Person prPeerReviewer = new Person();
prPeerReviewer = (Person)initform.gpPeerReviewer[0];
//PeerReviewer = prPeerReviewer.AccountId.GetValue(0).ToString();
PeerReviewer = prPeerReviewer.AccountId;
Person prCC = new Person();
prCC = (Person)initform.gpCC[0];
//CC = prCC.AccountId.GetValue(0).ToString();
CC = prCC.AccountId;
Instructions = initform.txtInstructions;
34. go to the top of PeerReviewWorkflow.vb and add the following code:
VISUAL BASIC
------------
imports System.Xml.Serialization
imports System.Xml
VISUAL C#
---------
using System.Xml.Serialization;
using System.Xml;
Next, we will add code to the InitiatorApproval state.
35. go back to the design surface and double-click InitiatorApprovalInitialization to drill down into it
36. double-click createInitiatorApprovalTask to be taken to its MethodInvoking code stub. Copy and paste
the following code in it.
VISUAL BASIC
------------
createInitiatorApprovalTask_TaskId = Guid.NewGuid
createInitiatorApprovalTask_TaskProperties = new SPWorkflowTaskProperties
createInitiatorApprovalTask_TaskProperties.Title = "Initiator Approval of " & workflowProperties.Item.File.Name
createInitiatorApprovalTask_TaskProperties.AssignedTo = workflowProperties.Originator
createInitiatorApprovalTask_TaskProperties.Description = ""
createInitiatorApprovalTask_TaskProperties.TaskType = 0
createInitiatorApprovalTask_TaskProperties.ExtendedProperties("txtInstructions") = Instructions
VISUAL C#
---------
createInitiatorApprovalTask_TaskId = Guid.NewGuid();
createInitiatorApprovalTask_TaskProperties = new SPWorkflowTaskProperties();
createInitiatorApprovalTask_TaskProperties.Title = "Initiator Approval of " + workflowProperties.Item.File.Name;
createInitiatorApprovalTask_TaskProperties.AssignedTo = workflowProperties.Originator;
createInitiatorApprovalTask_TaskProperties.Description = "";
createInitiatorApprovalTask_TaskProperties.TaskType = 0;
createInitiatorApprovalTask_TaskProperties.ExtendedProperties["txtInstructions"] = Instructions;
37. go back to the design surface and double-click InitiatorApprovalActivities to drill down into it
38. double-click onInitiatorApprovalChanged to be taken to its Invoked code stub. Copy and paste
the following code in it.
VISUAL BASIC
------------
onInitiatorApprovalChanged_AfterProperties = onInitiatorApprovalChanged.AfterProperties
onInitiatorApprovalChanged_BeforeProperties = onInitiatorApprovalChanged.BeforeProperties
VISUAL C#
---------
onInitiatorApprovalChanged_AfterProperties = onInitiatorApprovalChanged.AfterProperties;
onInitiatorApprovalChanged_BeforeProperties = onInitiatorApprovalChanged.BeforeProperties;
39. go back to the design surface and click ifInitiatorApproved and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type InitiatorApproved and hit enter
b. you will be taken to the code stub for InitiatorApproved routine; copy and paste the following
code into it:
VISUAL BASIC
------------
If onInitiatorApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "accepted" Then
e.Result = True
End If
VISUAL C#
---------
if (onInitiatorApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "accepted")
{
e.Result = true;
}
Next, we will add code to the PeerReviewerApproval state.
40. go back to the design surface and double-click PeerReviewerApprovalInitialization to drill down into it
41. double-click createPeerReviewerApprovalTask to be taken to its MethodInvoking code stub. Copy and paste
the following code in it.
VISUAL BASIC
------------
createPeerReviewerApprovalTask_TaskId = Guid.NewGuid
createPeerReviewerApprovalTask_TaskProperties = new SPWorkflowTaskProperties
createPeerReviewerApprovalTask_TaskProperties.Title = "Peer Reviewer Approval of " & workflowProperties.Item.File.Name
createPeerReviewerApprovalTask_TaskProperties.AssignedTo = PeerReviewer
createPeerReviewerApprovalTask_TaskProperties.Description = ""
createPeerReviewerApprovalTask_TaskProperties.TaskType = 0
createPeerReviewerApprovalTask_TaskProperties.ExtendedProperties("txtInstructions") = Instructions
VISUAL C#
---------
createPeerReviewerApprovalTask_TaskId = Guid.NewGuid();
createPeerReviewerApprovalTask_TaskProperties = new SPWorkflowTaskProperties();
createPeerReviewerApprovalTask_TaskProperties.Title = "Peer Reviewer Approval of " + workflowProperties.Item.File.Name;
createPeerReviewerApprovalTask_TaskProperties.AssignedTo = PeerReviewer;
createPeerReviewerApprovalTask_TaskProperties.Description = "";
createPeerReviewerApprovalTask_TaskProperties.TaskType = 0;
createInitiatorApprovalTask_TaskProperties.ExtendedProperties["txtInstructions"] = Instructions;
42. go back to the design surface and double-click PeerReviewerApprovalActivities to drill down into it
43. double-click onPeerReviewerApprovalChanged to be taken to its Invoked code stub. Copy and paste
the following code in it.
VISUAL BASIC
------------
onPeerReviewerApprovalChanged_AfterProperties = onPeerReviewerApprovalChanged.AfterProperties
onPeerReviewerApprovalChanged_BeforeProperties = onPeerReviewerApprovalChanged.BeforeProperties
VISUAL C#
---------
onPeerReviewerApprovalChanged_AfterProperties = onPeerReviewerApprovalChanged.AfterProperties;
onPeerReviewerApprovalChanged_BeforeProperties = onPeerReviewerApprovalChanged.BeforeProperties;
44. go back to the design surface and click ifPeerReviewerApproved and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type PeerReviewerApproved and hit enter
b. you will be taken to the code stub for PeerReviewerApproved routine; copy and paste the following
code into it:
VISUAL BASIC
------------
If onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "accepted" Then
e.Result = True
End If
VISUAL C#
---------
if (onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "accepted")
{
e.Result = true;
}
45. go back to the design surface and click ifPeerReviewerRejected and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type PeerReviewerRejected and hit enter
b. you will be taken to the code stub for PeerReviewerApproved routine; copy and paste the following
code into it:
VISUAL BASIC
------------
If onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "rejected" Then
e.Result = True
End If
VISUAL C#
---------
if (onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "rejected")
{
e.Result = true;
}
46. sign the assembly
a. right-click the project, then click properties
b. click Signing
c. if Sign the assembly is not already checked...
d. check Sign the assembly
e. choose <new> from the dropdown
f. enter MyKeyFile for the key file name
g. uncheck Protect my key file with a password
h. click OK
47. follow instructions above for creating the workflow's Initiation and Task forms using Microsoft Office InfoPath
48. If developing in Visual Studio 2005: copy PeerReviewInit.xsn and PeerReviewTask.xsn to DeploymentFiles\FeatureFiles folder
49. be sure feature.xml is set up correctly
a. title: Agenda Item Peer Review feature
b. description: This feature installs the Agenda Item Peer Review workflow
c. if deploying via Visual Studio 2008:
under the <ElementManifests> tag, add:
<ElementFile Location="PeerReviewInit.xsn" />
<ElementFile Location="PeerReviewTask.xsn" />
50. be sure workflow.xml is set up correctly
a. name: AgendaItemPeerReview
b. description: This is the Agenda Item Peer Review workflow
c. CodeBesideClass: AgendaItemPeerReview.PeerReviewWorkflow
d. after CodeBesideAssembly, you must have the following:
TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"
e. after TaskListContentTypeId, you must have the following:
AssociationUrl="_layouts/CstWrkflIP.aspx"
InstantiationUrl="_layouts/IniWrkflIP.aspx"
ModificationUrl="_layouts/ModWrkflIP.aspx"
f. copy initiation form's urn into the <Association_FormURN> and <Instantiation_FormURN> tags
g. copy task form's urn into the <Task0_FormURN> tag
51. deploy solution
a. Visual Studio 2005: right click project, click Properties, click Build Events, change NODEPLOY to
DEPLOY at the end of the Post-build event command line, click Build, click Rebuild Solution
b. Visual Studio 2008: click Build, then click Deploy Solution
52. The first time you deploy it, the AgendaItemPeerReview assembly is added to the GAC. The workflow.xml
file needs the assembly's public key token.
a. Start, Settings, Control Panel, Administrative Tools, Microsoft .NET Framework 2.0 Configuration
b. click Manage the Assembly Cache
c. click View List of Assemblies in the Assembly Cache
d. right-click on AgendaItemPeerReview and click Properties
e. copy the public key token and click OK
f. paste the public key token in workflow.xml where you find PublicKeyToken
g. redeploy solution (step 51)
Another method for retrieving the assembly's public key token
-------------------------------------------------------------
If you want to skip step 52, retrieve the public key token using the sn.exe utility then
update workflow.xml (step 50) with it
1. copy sn.exe from c:\program files\microsoft visual studio 8\sdk\v2.0\bin to d:\
2. change directory to d:\asp\agendaitempeerreview\agendaitempeerreview\bin\debug
3. d:\sn -T agendaitempeerreview.dll
4. copy (or type) the resulting public key token into the workflow.xml
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
Tags:
sharepoint,
state machine workflow
How to create a SharePoint State Machine Workflow: Part 3 - Create the task form
In Part 3, we will create the workflow's task form. In Part 4, we will configure this form to be called as the task form.
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
1. load Microsoft Office InfoPath 2007
2. Under Design a form, click Design a Form Template, click Blank, then click OK
3. click Insert, Layout Table..., 2 columns, 3 rows, click OK
4. add three buttons to the right column, last row
5. in Design Tasks, click Data Source
a. double-click myFields and rename it to TaskForm and click OK
b. right-click TaskForm and click Add...
c. for name, type status
d. click OK
6. double-click the first button
a. change Label to Approve
b. click Rules...
c. click Add...
d. click Add Action...
e. select Submit using a data connection from dropdown
f. click Add...
g. be sure Create a new connection to submit data is selected and click Next
h. select To the hosting environment, such as an ASP.NET page or a hosting application
i. leave Submit as the name for the data connection and click Finish
j. click OK
k. click Add Action...
l. select Set a field's value
m. click icon to the right of the Field textbox
n. click status and click OK
o. for value, type: accepted
p. click OK
q. click Add Action...
r. select Close the form from the dropdown
s. uncheck If changes have not been saved...
t. click OK
u. click OK
v. click OK
w. click OK
7. double-click the second button
a. change Label to Reject
b. click Rules...
c. click Add...
d. click Add Action...
e. select Submit using a data connection from dropdown
f. click Add...
g. be sure Create a new connection to submit data is selected and click Next
h. select To the hosting environment, such as an ASP.NET page or a hosting application
i. leave Submit as the name for the data connection and click Finish
j. click OK
k. click Add Action...
l. select Set a field's value
m. click icon to the right of the Field textbox
n. click status and click OK
o. for value, type: rejected
p. click OK
q. click Add Action...
r. select Close the form from the dropdown
s. uncheck If changes have not been saved...
t. click OK
u. click OK
v. click OK
w. click OK
8. double-click the third button
a. change Label to Cancel
b. click Rules...
c. click Add...
d. click Add Action...
e. select Close the form from the dropdown
f. uncheck If changes have not been saved...
g. click OK
h. click OK
i. click OK
j. click OK
9. in left column, first row, type: Instructions
10. in left column, second row, type: Comments
11. in the right column, first row, drop a text box control into it
a. double-click the text box control
b. change field name to txtInstructions
c. click Display tab and check Multi-line
d. click OK
e. drag the bottom edge of the control down a little ways so a few lines of text will show
12. in the right column, second row, drop a text box control into it
a. double-click the text box control
b. change field name to txtComments
c. click Display tab and check Multi-line
d. click OK
e. drag the bottom edge of the control down a little ways so a few lines of text will show
13. when the task form loads, we want txtInstructions to populate automatically with what was typed in
to txtInstructions on the initiation form
a. on the Desktop, create a file called ItemMetadata.xml (CASE IS EXTREMELY IMPORTANT!!!) with the following text in it:
<z:row xmlns:z="#RowsetSchema" ows_txtInstructions="" />
b. in Design Tasks, click Data Source, click Manage Data Connections... (near bottom)
c. click Add
d. click Create a connection to Receive data
e. click Next
f. be sure XML document is selected and click Next
g. click Browse and select ItemMetadata.xml from Desktop
h. click Next
i. be sure Include the data as a resource file... is selected and click Next
j. click Finish
k. click Close
l. double-click txtInstructions
m. click the fx button next to the value text field
n. click Insert Field or Group...
o. change data source in drop down to ItemMetadata (Secondary)
p. click :ows_txtInstructions
q. click Ok
r. click Ok
s. click Ok
Note: In the code that creates the tasks in the workflow, we will add txtInstructions to the task's
ExtendedProperties and populate it with txtInstructions from the initiation form. ItemMetaData.xml
will get this value from the task's txtInstructions property (added in ExtendedProperties) and will
then populate the task form's txtInstructions textbox on load.
14. make form be able to open in a browser or in a client application
a. in Design Tasks, click Design Checker, click Change Compatibility Settings...
b. in the Compatibility category, check Design a form template that can be opened in a browser or InfoPath
c. in the Security and Trust category, uncheck Automatically determine security level, click Full Trust
d. click OK
15. save form in My Documents as PeerReviewTask.xsn
16. retrieve the form's id
a. click File, Properties
b. copy text in ID textbox into notepad to be used in a later step. it should look like this:
urn:schemas-microsoft-com:office:infopath:PeerReviewTask:-myXSD-2008-07-30T18-33-30
17. publish the InfoPath form
a. click File, Publish
b. select To a network location
c. click Next
d. browse to the workflow project's location and name the file PeerReviewTask.xsn
e. click OK
f. click Next
g. delete the path in the textbox and click Next
h. click Publish
i. click Close
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
How to create a SharePoint State Machine Workflow: Part 2 - Create the initiation form
In Part 2, we will create the workflow's initiation form. In Part 4, we will configure this form to be called in two locations: first, when a user associates the workflow with a document library, and second, when a user instantiates the workflow on a particular document. Thus, we are using the same form as both the association form and the initiation form. Its possible to use different forms collecting different information, but for simplicity, we will use the same form for both.
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
1. load Microsoft Office InfoPath 2007
2. Under Design a form, click Design a Form Template, click Blank, then click OK
3. if the Contact Selector control has not been added to the controls list, you will need to add it
a. on the Design Tasks pane, click Controls; if Contact Selector does not appear
under the Custom controls in the bottom of the list, continue with the following
steps. If it does appear there (ie you already added it), skip to step 4
b. click Add or Remove Custom Control...
c. click Add button
d. click ActiveX Control and click Next
e. click Contact Selector from the list and click Next
f. click Don't include a .cab file and click Next
g. click Value and click Next
h. in the Field or group type dropdown, select Field or Group (any data type) and click Finish, Close, OK
4. click Insert, Layout Table..., 2 columns, 4 rows, click OK
5. add two buttons to the right column, last row
6. double-click the first button
a. change Label to OK
b. click Rules...
c. click Add...
d. click Add Action...
e. select Submit using a data connection from dropdown
f. click Add...
g. be sure Create a new connection to submit data is selected and click Next
h. select To the hosting environment, such as an ASP.NET page or a hosting application
i. leave Submit as the name for the data connection and click Finish
j. click OK
k. click Add Action...
l. select Close the form from the dropdown
m. uncheck If changes have not been saved...
n. click OK
o. click OK
p. click OK
q. click OK
r. stretch out OK button
7. double-click the second button
a. change Label to Cancel
b. click Rules...
c. click Add...
d. click Add Action...
e. select Close the form from the dropdown
f. uncheck If changes have not been saved...
g. click OK
h. click OK
i. click OK
j. click OK
8. in left column, first row, type: Peer Reviewer
9. in left column, second row, type: CC
10. in left column, third row, type: Instructions
11. in the right column, third row, drop a text box control into it
a. double-click the text box control
b. change field name to txtInstructions
c. click Display tab and check Multi-line
d. click OK
12. resize left column to be smaller than right column, resize txtInstructions to allow
for multiple lines
13. in the right column, first row, drop a Contact Selector control into it
a. double-click the control, rename it to gpPeerReviewer, click OK
14. in the right column, second row, drop a Contact Selector control into it
a. double-click the control, rename it to gpCC, click OK
15. in Design Tasks, click Data Source
a. double-click myFields and rename it to InitForm and click OK
b. right click group1 and delete it
c. right click gpPeerReviewer and click Add
d. name is Person, the type is Group, check Repeating
e. right click Person, click Add..., enter name as DisplayName, click OK
f. right click Person, click Add..., enter name as AccountId, click OK
g. right click Person, click Add..., enter name as AccountType, click OK
h. right click Person, click Reference..., click gpCC, click OK
16. make form be able to open in a browser or in a client application
a. in Design Tasks, click Design Checker, click Change Compatibility Settings...
b. in the Compatibility category, check Design a form template that can be opened in a browser or InfoPath
c. in the Security and Trust category, uncheck Automatically determine security level, click Full Trust
d. click OK
17. create the context for the form so it knows where to retrieve people's names from:
a. open Notepad and type in the following and save it as Context.xml in the same folder as the workflow:
<Context
isStartWorkflow="true"
isRunAtServer="true"
provideAllFields="true"
siteUrl="http://sharepoint2007:100"
/>
Note: be sure siteUrl is for your site!!!
18. add a secondary data source using Context.xml so form can retrieve contact data:
a. in Design Tasks, Data Source, click Manage Data Connections...
b. click Add...
c. Create a new connection to Receive Data
d. click Next
e. click XML document
f. click Next
g. browse to Context.xml
h. click Next
i. click Include the data as a resource file in the form template or template part
j. click Next
k. leave Context as the name of the data connection
l. ensure Automatically retrieve data when form is opened is checked
m. click Finish
n. click Close
19. save form in My Documents as PeerReviewInit.xsn
20. retrieve the form's id
a. click File, Properties
b. copy text in ID textbox into notepad to be used in a later step. it should look like this:
urn:schemas-microsoft-com:office:infopath:PeerReviewInit:-myXSD-2008-07-23T13-50-07
21. publish the InfoPath form
a. click File, Publish
b. select To a network location
c. click Next
d. browse to the workflow project's location and name the file PeerReviewInit.xsn
e. click OK
f. click Next
g. delete the path in the textbox and click Next
h. click Publish
i. click Close
22. create the schema for the form so the workflow can reference the contents of the form
a. click File, Save as source files...
b. browse to the folder where the workflow is
c. click OK
d. close Microsoft InfoPath (you will not be able to make any changes to the form once InfoPath is closed)
e. click Start, Run, type CMD and click OK
f. change folder to the workflow's folder
g. type: xsd myschema.xsd /c /language:VB (Note: you may have to find xsd.exe on your HD first)
h. rename the resulting myschema.vb to InitForm.vb
23. add file to workflow project
a. in Visual Studio in Solution Explorer, Show All Files
b. right-click InitForm.vb and click Include In Project
Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails
How to create a SharePoint State Machine Workflow: Part 1 - Introduction
A while back I was tasked with creating a SharePoint workflow that would enable peer review of a document. (BTW, this whole article series will be all about creating workflows with Visual Studio, not SharePoint Designer.) An initiator would assign the workflow to a document in a document library and set who they wanted to peer review it. Now, if you've searched the internet for SharePoint workflows, you've undoubtedly seen that one sequential workflow that always pops up in examples. The problem is that sometimes a sequential workflow doesn't cut it - you just might need a state machine workflow. Imagine having a workflow where someone wants to send a document out for approval to three people sequentially, like a manager, a division director, and a department director. After the manager approves it, it goes to the division director, then when that person approves it, the department director gets it. But what if the division director rejects it? You might want it to bounce back to the manager. Similarly, if the department director rejects it, it goes back to the division director. The state machine workflow is the way to do this as each person will be a state.
This will be a six part series on how to create a SharePoint state machine workflow, although only the first four parts are necessary to create a functioning workflow
.
This example will employ a simple peer review model. An initiator will start a workflow on a document in a document library, assigning the person they want to act as the peer reviewer in the Initiation form. The workflow then starts. The peer reviewer will get a task. If the peer reviewer approves the task, the workflow is complete. If the peer review rejects the task, the initiator will get a task. In theory, the initiator could make changes to their document, and once they are done, they would approve the task, thus moving the flow back to the peer reviewer again. Once again, the peer reviewer would need to accept the task (which ends the workflow) or reject it (which sends the flow back to the initiator).
You are currently reading Part 1 of this series.
Part 2 will focus on using InfoPath to create the workflow's Initiation form.
Part 3 will focus on using InfoPath to create the workflow's Task form.
Part 4 will focus on creating the state machine workflow itself.
After part 4, you will have a fully functioning workflow. The following two parts are more like enhancements, but you'll likely want them.
Part 5 will focus on adding workflow history logging to the workflow.
Part 6 will focus on adding task notification emails to the workflow.
This will be a six part series on how to create a SharePoint state machine workflow, although only the first four parts are necessary to create a functioning workflow
.
This example will employ a simple peer review model. An initiator will start a workflow on a document in a document library, assigning the person they want to act as the peer reviewer in the Initiation form. The workflow then starts. The peer reviewer will get a task. If the peer reviewer approves the task, the workflow is complete. If the peer review rejects the task, the initiator will get a task. In theory, the initiator could make changes to their document, and once they are done, they would approve the task, thus moving the flow back to the peer reviewer again. Once again, the peer reviewer would need to accept the task (which ends the workflow) or reject it (which sends the flow back to the initiator).
You are currently reading Part 1 of this series.
Part 2 will focus on using InfoPath to create the workflow's Initiation form.
Part 3 will focus on using InfoPath to create the workflow's Task form.
Part 4 will focus on creating the state machine workflow itself.
After part 4, you will have a fully functioning workflow. The following two parts are more like enhancements, but you'll likely want them.
Part 5 will focus on adding workflow history logging to the workflow.
Part 6 will focus on adding task notification emails to the workflow.
Tags:
sharepoint,
state machine workflow,
workflow
Tuesday, June 23, 2009
How to set a SharePoint web part's list at run time
Let's say you have a web part that must have a particular custom list you created in order for the web part to function. For instance, in the web part's CreateChildControls() routine, maybe you have some code similar to this:
So in the above example, the required custom list is called "Contact List". You may run into a few problems with this:
First off, we need to create a custom property for your Contact List web part. Below is the code for the custom property.
So, the above code will add a new property called Contact List Data Source to the web part's properties you see when you click the web part in Edit Mode and click Modify Shared Web Part. Scroll down to the section Miscellaneous and you will see it. (Currently it is simply a textbox but it is possible to make it a dropdown list that lists available lists on the site).
Now, if we go back to the first code snippet above that gets the list, we just change it to reference our new property:
While this makes my web part dynamic in terms of what list to look at, I still have a problem. What if the user just added the web part to a page? The property is going to be empty initially until the user sets it. So what does the user see? Or, what if the list the user specifies in the property does not exist or is an invalid list?
So, the next thing I do is create a function called ContactListIsValid() which will test to make sure the ContactListDataSource property the user specified is valid. If it is, the function will return true and the above code snippet will fire. If it is not valid, the function will return false and code will fire off that displays a message to the user telling them they need to set the ContactListDataSource property to a valid list.
First, you will see ContactListIsValid(), then below that I will show you how I use it for displaying error messages. Note that this function verifies the list exists and has the required fields.
Finally, below is the code on how I use this in CreateChildControls().
A final thought. Notice the error message tells the user to set the property to a list based on the ContactListTemplate list template. I created a list template from the Contact List custom list, so that way the user can create their contact list from the list template to make sure they have all the necessary fields. To do this, I went to the List Settings of Contact List and selected Save As List Template.
Well, that's it! I wouldn't be surprised if people have their own way of doing all this, but I like to go for the simplest, most direct approach. I hope this helps!
'get the site the web part is running on
Dim oWeb As SPWeb
oWeb = Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(Context)
'get the list we are going to work with
Dim oList As SPList
oList = oWeb.Lists("Contact List")
...other code that loops through oList and displays the different field values...
So in the above example, the required custom list is called "Contact List". You may run into a few problems with this:
- What if the required list doesn't exist?
- What if the list exists, but it doesn't have the fields needed by the web part?
- Every time you drop the web part on a page, it must have that list when it might be beneficial to have some flexibility with the list the web part is pointing to. A good example of this would be a site with a Marketing page and a Sales page. On the Marketing page, you want to display Marketing Contacts and on the Sales page you want to display Sales Contacts. Marketing Contacts and Sales Contacts are two separate lists.
First off, we need to create a custom property for your Contact List web part. Below is the code for the custom property.
Dim strContactListDataSource As String = ""
<WebBrowsable(True), Personalizable(True), FriendlyName("Contact List Data Source")> _
Property ContactListDataSource() As String
Get
Return strContactListDataSource
End Get
Set(ByVal value As String)
strContactListDataSource = value
End Set
End Property
So, the above code will add a new property called Contact List Data Source to the web part's properties you see when you click the web part in Edit Mode and click Modify Shared Web Part. Scroll down to the section Miscellaneous and you will see it. (Currently it is simply a textbox but it is possible to make it a dropdown list that lists available lists on the site).
Now, if we go back to the first code snippet above that gets the list, we just change it to reference our new property:
'get the site the web part is running on
Dim oWeb As SPWeb
oWeb = Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(Context)
'get the list we are going to work with
Dim oList As SPList
oList = oWeb.Lists(ContactListDataSource)
...other code that loops through oList and displays the different field values...
While this makes my web part dynamic in terms of what list to look at, I still have a problem. What if the user just added the web part to a page? The property is going to be empty initially until the user sets it. So what does the user see? Or, what if the list the user specifies in the property does not exist or is an invalid list?
So, the next thing I do is create a function called ContactListIsValid() which will test to make sure the ContactListDataSource property the user specified is valid. If it is, the function will return true and the above code snippet will fire. If it is not valid, the function will return false and code will fire off that displays a message to the user telling them they need to set the ContactListDataSource property to a valid list.
First, you will see ContactListIsValid(), then below that I will show you how I use it for displaying error messages. Note that this function verifies the list exists and has the required fields.
Private Function ContactListIsValid() As Boolean
'PURPOSE: returns true if specified contact list is valid for the web part
Try
'get the site the web part is running on
Dim oWeb As SPWeb
oWeb = Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(Context)
'get the list specified by user in property, if list is not found in site, flow will hit Catch
Dim oList As SPList
oList = oWeb.Lists(ContactListDataSource)
'check if fields exist in list
Dim blnContactNameFound As Boolean = False
Dim blnJobTitleFound As Boolean = False
Dim blnEmailAddressFound As Boolean = False
Dim blnPhoneFound As Boolean = False
Dim blnFaxFound As Boolean = False
'loop through the fields in the list and check to see if all the required fields are in the list
For Each oField As SPField In oList.Fields
If Not oField.Hidden And Not oField.ReadOnlyField And oField.Type <> SPFieldType.Attachments Then
If oField.Title = "Contact Name" Then
blnContactNameFound = True
End If
If oField.Title = "Job Title" Then
blnJobTitleFound = True
End If
If oField.Title = "Email Address" Then
blnEmailAddressFound = True
End If
If oField.Title = "Phone" Then
blnPhoneFound = True
End If
If oField.Title = "Fax" Then
blnFaxFound = True
End If
End If
Next
'if all fields were found, return true
If blnContactNameFound And blnJobTitleFound And blnEmailAddressFound And blnPhoneFound And blnFaxFound Then
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
Finally, below is the code on how I use this in CreateChildControls().
'if ContactListSource is a valid list, display controls
If ContactListIsValid() Then
Try
'get the site the web part is running on
Dim oWeb As SPWeb
oWeb = Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(Context)
'get the list we are going to work with
Dim oList As SPList
oList = oWeb.Lists(ContactListDataSource)
...other code that loops through oList and displays the different field values...
Catch ex As Exception
Dim lblErrorMessage As New Label
lblErrorMessage.ForeColor = Drawing.Color.Red
lblErrorMessage.Text = "Error: " & ex.Message & _
"<BR><BR>" & "Details: " & ex.ToString
Controls.Add(lblErrorMessage)
End Try
Else
'else display message telling user they must configure the ContactListDataSource property
Dim lblConfigureMessage As New Label
lblConfigureMessage.ForeColor = Drawing.Color.Red
lblConfigureMessage.Text = "<BR> <BR>" & "You must configure the Contact List Data Source property. " & _
"<BR> <BR>" & "Please click Modify Shared Web Part -> Miscellaneous -> Contact List Data Source " & _
"and set it to a list based on the ContactListTemplate list template."
Controls.Add(lblConfigureMessage)
End If
A final thought. Notice the error message tells the user to set the property to a list based on the ContactListTemplate list template. I created a list template from the Contact List custom list, so that way the user can create their contact list from the list template to make sure they have all the necessary fields. To do this, I went to the List Settings of Contact List and selected Save As List Template.
Well, that's it! I wouldn't be surprised if people have their own way of doing all this, but I like to go for the simplest, most direct approach. I hope this helps!
Tuesday, June 16, 2009
RequiredFieldValidator in connected web parts
I plan on posting a tutorial on how to easily create connected web parts, but I wanted to write a short post on a unique issue I just came across, along with a simple solution.
Normally, when you add a RequiredFieldValidator to a form, it triggers when there is an attempted postback for that form. However, I have a scenario where I have two web parts, one called Contact List (this is the provider web part; it simply pulls data from a custom list library called Contact List and displays it) and another web part called Email Contact (this is the consumer webpart; it has a form where a user can enter their name, email, and message they want to email a contact). The Contact List web part provides a person while the Email Contact web part consumes a person. So when a user clicks a person in the Contact List, a postback is performed and the Email Contact web part will display that person's information above the form. A user could then enter their message, then click the Send button and it would email to the selected contact person.
Ok, so did you notice the problem yet? If the Email Contact web part has required field validators on its form fields (ie required on the submitter's name, email, and message), then when a user clicks a person in the Contact List web part, the requiredfieldvalidators in the Email Contact web part will prevent the postback from occurring, thus the person is never sent from the Contact List web part to the Email Contact web part. (Note: this scenario assumed that Contact List and Email Contact are on the same web page in the same WebPartZone).
The solution ended up being pretty simple. I am not going into the details of my specific implementation because the concept here is what it important (plus, I will post the code for this web part anyway in my article on connected web parts that I will do very soon). The solution is to use the ValidationGroup property of the RequiredFieldValidators and the Send button that saves/sends the message. To get this to work, I did the following:
In the Email Contact web part (all this is done in CreateChildControls):
1. create the RequiredFieldValidator controls for each field that needs it
2. add the RequiredFieldValidator controls to the web part's Controls collection
3. set their properties like ErrorMessage and ControlToValidate as you normally would
4. set their ValidationGroup property to "EmailContactValidationGroup"
5. when you create the form's button that saves/sends the email, set the button's ValidationGroup property also to "EmailContactValidationGroup"
The key here is to make sure the ValidationGroup is the same for the button and validators. This way, the validators will only fire off when the button is pressed!
Normally, when you add a RequiredFieldValidator to a form, it triggers when there is an attempted postback for that form. However, I have a scenario where I have two web parts, one called Contact List (this is the provider web part; it simply pulls data from a custom list library called Contact List and displays it) and another web part called Email Contact (this is the consumer webpart; it has a form where a user can enter their name, email, and message they want to email a contact). The Contact List web part provides a person while the Email Contact web part consumes a person. So when a user clicks a person in the Contact List, a postback is performed and the Email Contact web part will display that person's information above the form. A user could then enter their message, then click the Send button and it would email to the selected contact person.
Ok, so did you notice the problem yet? If the Email Contact web part has required field validators on its form fields (ie required on the submitter's name, email, and message), then when a user clicks a person in the Contact List web part, the requiredfieldvalidators in the Email Contact web part will prevent the postback from occurring, thus the person is never sent from the Contact List web part to the Email Contact web part. (Note: this scenario assumed that Contact List and Email Contact are on the same web page in the same WebPartZone).
The solution ended up being pretty simple. I am not going into the details of my specific implementation because the concept here is what it important (plus, I will post the code for this web part anyway in my article on connected web parts that I will do very soon). The solution is to use the ValidationGroup property of the RequiredFieldValidators and the Send button that saves/sends the message. To get this to work, I did the following:
In the Email Contact web part (all this is done in CreateChildControls):
1. create the RequiredFieldValidator controls for each field that needs it
2. add the RequiredFieldValidator controls to the web part's Controls collection
3. set their properties like ErrorMessage and ControlToValidate as you normally would
4. set their ValidationGroup property to "EmailContactValidationGroup"
5. when you create the form's button that saves/sends the email, set the button's ValidationGroup property also to "EmailContactValidationGroup"
The key here is to make sure the ValidationGroup is the same for the button and validators. This way, the validators will only fire off when the button is pressed!
Saturday, June 13, 2009
How to populate a custom Contact List from AD
In a recent project at work, I was required to create a custom Contact List. The only problem was that I didn't want my SharePoint admins to have to go to the list, then click New -> Item and have to sit there and enter in the person's name, email address, title, etc, when all this information is already sitting over in AD (Active Directory) to begin with.
So, I wanted to provide my SharePoint site admins with the ability to search AD, select a person (or people), then with the click of a button, add them into my Contact List, which is a custom list library. My solution ended up being quite straightforward - create a web part with two controls, 1. a PeopleEditor control (the standard SharePoint control for selecting people from AD; it has those two little buttons in the lower right for checking the name you enter and a browse button to let you do searches) and 2. a Button control which will take whatever people have been entered into the PeopleEditor and save them into the Contact List.
First off, this assumes you have created a custom list called "Contact List" on your SharePoint site. The fields are all text fields, called "Name", "Job Title", and "Email Address".
1. Load Visual Studio 2008
2. Click File -> New -> Project... -> Visual Basic -> SharePoint -> Web Part
3. For the name, I called mine AddContactFromAD
4. Notice the web part was named WebPart1. Do a search and replace across the whole project and change WebPart1 to AddContactFromAD wherever it is found. While that just changed the contents of the files in the project, you will notice there is still a folder called WebPart1 with files inside all named WebPart1 with different file extensions. Manually rename the folder and the base names of those files (leaving the extensions alone for each).
5. Copy and paste the code below.
6. In the Solution Explorer, right click the project and click Properties, click the Debug tab on the left, then in "Start browser with URL:" type in the name of the SharePoint site you want to deploy the web part to.
7. Hit F5 to build and deploy the web part.
Code for AddContactFromAD.vb:
1. under your inherits statement and above your constructor, copy and paste:
2. in CreateChildControls(), copy and paste:
3. below CreateChildControls(), copy and paste the following subroutine.
So, I wanted to provide my SharePoint site admins with the ability to search AD, select a person (or people), then with the click of a button, add them into my Contact List, which is a custom list library. My solution ended up being quite straightforward - create a web part with two controls, 1. a PeopleEditor control (the standard SharePoint control for selecting people from AD; it has those two little buttons in the lower right for checking the name you enter and a browse button to let you do searches) and 2. a Button control which will take whatever people have been entered into the PeopleEditor and save them into the Contact List.
First off, this assumes you have created a custom list called "Contact List" on your SharePoint site. The fields are all text fields, called "Name", "Job Title", and "Email Address".
1. Load Visual Studio 2008
2. Click File -> New -> Project... -> Visual Basic -> SharePoint -> Web Part
3. For the name, I called mine AddContactFromAD
4. Notice the web part was named WebPart1. Do a search and replace across the whole project and change WebPart1 to AddContactFromAD wherever it is found. While that just changed the contents of the files in the project, you will notice there is still a folder called WebPart1 with files inside all named WebPart1 with different file extensions. Manually rename the folder and the base names of those files (leaving the extensions alone for each).
5. Copy and paste the code below.
6. In the Solution Explorer, right click the project and click Properties, click the Debug tab on the left, then in "Start browser with URL:" type in the name of the SharePoint site you want to deploy the web part to.
7. Hit F5 to build and deploy the web part.
Code for AddContactFromAD.vb:
1. under your inherits statement and above your constructor, copy and paste:
WithEvents btnAddUsers As New Button
Dim peADLookUp As New PeopleEditor
Dim peADLookUp As New PeopleEditor
2. in CreateChildControls(), copy and paste:
'create and add message to web part
Dim lblMessage As New Label
lblMessage.Text = "Add contact(s) from Active Directory into Contact List"
Controls.Add(lblMessage)
'add PeopleEditor control to web part (it was created above)
peADLookUp.Rows = 1
Controls.Add(peADLookUp)
'add button to web part (it was created above)
btnAddUsers.Text = "Add user(s) to Contact List"
Controls.Add(btnAddUsers)
Dim lblMessage As New Label
lblMessage.Text = "Add contact(s) from Active Directory into Contact List"
Controls.Add(lblMessage)
'add PeopleEditor control to web part (it was created above)
peADLookUp.Rows = 1
Controls.Add(peADLookUp)
'add button to web part (it was created above)
btnAddUsers.Text = "Add user(s) to Contact List"
Controls.Add(btnAddUsers)
3. below CreateChildControls(), copy and paste the following subroutine.
Sub btnAddUsers_Click() Handles btnAddUsers.Click
'PURPOSE: add selected users to Contact List
'get the site the web part is running on
Dim oWeb As SPWeb
oWeb = Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(Context)
'get the list where we want to save the person to
Dim oList As SPList
oList = oWeb.Lists("Contact List")
Dim strDisplayName As String = ""
Dim strEmail As String = ""
Dim strTitle As String = ""
'loop through each entity in peADLookUp (note DisplayName,
' Email, and Title are all properties in AD)
For Each account As PickerEntity In peADLookUp.ResolvedEntities
'get values of current entity
strDisplayName = account.EntityData("DisplayName").ToString()
strEmail = account.EntityData("Email").ToString()
strTitle = account.EntityData("Title").ToString()
'create a new list item in Contact List
Dim oNewItem As SPListItem
oNewItem = oList.Items.Add()
'populate the new list item with values from the form
oNewItem.Item("Name") = strDisplayName
oNewItem.Item("Job Title") = strTitle
oNewItem.Item("Email Address") = strEmail
'update the list with the new item
oNewItem.Update()
Next
End Sub
Tags:
contact list,
entity,
list library,
PeopleEditor,
PickerEntity,
sharepoint,
web part
Subscribe to:
Posts (Atom)