Part 1: Creating the InfoPath Form
Part 2: Submitting the InfoPath Form to a Document Library
Part 3: Creating the Page Layout
In this series, I'm taking you through the step-by-step process of creating content in an InfoPath form that can be published on the Web. To recap where our series has taken us so far, we have:
- Created an InfoPath form that allows users to enter information about their newborn baby.
- Created the ability for the informatin from the form to be saved to a SharePoint document library when the form is submitted.
- Created a Publishing Page Layout that allows informaion about the newborn to be published to an external site.
Today I'll be showing you the last step in the process, which is the glue that holds it all together: a custom workflow that will create a Publishing Page in the Pages Library, using the Page Layout we created in the last step, which will contain the information that was collected from the InfoPath form. In this example, I'll be writing my code in Visual Studio 2008 using C#.
Creating the Wokflow Project in Visual Studio
I've installed the Visual Studio Extensions for Windows SharePoint Services (called VSeWSS for short.) VSeWSS has been published for both VS 2005 and VS 2008, and contains a set of pre-fabricated Visual Studio templates for you to use, such as creating a SharePoint web part, a SharePoint list, etc. Today I'm going to be using the SharePoint Sequential Workflow project template.
The first step is to open up Visual Studio and say you want to create a new project. We'll name it "InfoPathPublishingWorkflow".

On the next screen, a dialog will ask you which library you want to debug your workflow on. You can choose the Cradle Roll document library where you saved your InfoPath forms.
Once you've created your new project, rename your "Workflow1" class to "InfoPathPublishingWorkflow". Click "OK" to the pop-up box that asks if you also want to update all references to "Worklow1" in your code.
You'll see a workflow diagram that looks like this:

If you notice, there's a red exclamation point beside the "onWorkflowActivated1" activity. That's because there's still a reference to Workflow1 lying around. Click on the activity and view the Properties window, and expand the "CorrelationToken" field. You'll see it still says "Workflow1". Click on Workflow1 and you'll see it's a drop down menu. Select "InfoPathPublishingWorkflow" from the menu, and you'll see the red exclamation points disappear. Also change the "Name" property under "WorkflowProperties" to "InfoPathPublishingWorkflow" as well. Open up the workflow.xml file and change the reference to Workflow1 there, too. Last but not least, right click on "InfoPathPublishingWorkflow.Designer.cs" and say "View Code". Do a search for "Workflow1" and replace it with "InfoPathPublishingWorkflow".
We're going to be referencing Publishing objects in our code, so we'll need to add a reference to the SharePoint Publishing dll to our Visual Studio project. Browse to the "ISAPI" folder in your "12 hive". Typically, the path is "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\ISAPI". Select the dll called "Microsoft.SharePoint.Publishing.dll".

The next step we're going to take is to drop a "Code" activity into our workflow. All we're doing is saying, "Once our workflow starts, do some stuff." Open up your Toolbox in Visual Studio, find the "Code" activity, and drop it on the Workflow Designer, directly following the onWorfkowActivated activity. For the sake of readability, we'll rename it "createPage". Double click on the activity in the designer, and it will create a method that we can use.
Go to the top of the page and add a "using" reference to Microsoft.SharePoint.Publishing and Microsoft.Sharepoint.Publishing.Fields. I'm going to walk you step-by-step through the code we're going to use to create the publishing page.
try
{
using (SPSite site = this.workflowProperties.Site)
{
using (SPWeb rootWeb = site.RootWeb)
{
}
}
}
To start with, we're going to wrap everything in a try/catch block for easier debugging. Then we're going to use the "using" blocks for better garbage collection. Inside the "using" blocks, add this:
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(rootWeb);
PageLayout[] layouts = pubWeb.GetAvailablePageLayouts();
for (int i = 0; i < layouts.Length; i++)
{
PageLayout layout = layouts[i];
if (layout.Title == "Cradle Roll")
{
}
}
The first thing we're going to do is use the Publishing API to convert the SPWeb object to a PublishingWeb object. We do this by calling the "GetPublishingWeb" method of the PublishingWeb object, and passing in our SPWeb object. (For the sake of our example, all our lists and libraries are stored in the site collection's root web site.) Next, we're going to loop through all the available Page Layouts on the site, looking for our "Cradle Roll" page layout, which we'll use to create a new publishing page.
SPListItem workflowItem = this.workflowProperties.Item;
string pageName = workflowItem["Babys_x0020_First_x0020_Name"].ToString() + workflowItem["Babys_x0020_Last_x0020_Name"].ToString();
SPQuery newQuery = new SPQuery();
newQuery.Query = "<Where><Eq><FieldRef Name='Title'/>" +
"<Value Type='Text'>" + pageName + "</Value></Eq></Where>";
SPListItemCollection pages = pubWeb.PagesList.GetItems(newQuery);
The first thing we're doing is creating a local reference to the list item that the workflow is executing against. Since we're going to be attaching our workflow to the Cradle Roll document library, the "workflow properties item" will be a list item in that library.
Next, we're coming up with a unique name for our Publishing Page in the Pages library. Granted, concatenating first and last name is not real unique, but it works for this example. Feel free to append other distinguishing information to ensure unique pages get created for each item in your document library. (In this example, if a page with the same name already exists, this workflow will overwrite it with new information.)
The next thing we're doing is creating a CAML query using the SPQuery object. (CAML stands for "Collaborative Application Markup Language", and is a way of querying the SharePoint database directly for items, using XML-style notation.) We're querying the Pages library to see if a page already exists for this particular baby.
try
{
cradleRollListItem = pages[0];
if (cradleRollListItem.File.CheckOutStatus == SPFile.SPCheckOutStatus.None)
{
cradleRollListItem.File.CheckOut();
}
else
{
return;
}
}
catch
{
PublishingPage cradleRollPage = pubWeb.GetPublishingPages().Add(pageName + ".aspx", layout);
cradleRollListItem = cradleRollPage.ListItem;
}
Unfortunately, if the query doesn't return any results, your result set's count won't be set to 0, it will simply be null. Since it's null, if you try to query the "Count" property of a "null" object, it will throw an error. We're just incorporating this into our code. If we don't get an error, we're assuming results were returned, so we'll use the first page we get back. Otherwise, we're going to create a new PublishingPage object, giving it a page name and a Page Layout to use, and adding it to the Pages library. In both cases, we're setting a local reference to that Page's SPListItem object that we can use later for setting its field values.
cradleRollListItem["Title"] = pageName;
SPContentType cradleRollContentType = rootWeb.ContentTypes["Cradle Roll"];
foreach (SPField siteColumn in cradleRollContentType.Fields
{
if (siteColumn.Title.StartsWith("Baby") || siteColumn.Title.StartsWith("Father") || siteColumn.Title.StartsWith("Mother"))
{
if (siteColumn.Title != "Baby Photo 1" && siteColumn.Title != "Baby Photo 2")
{
cradleRollListItem[siteColumn.Title] = workflowItem[siteColumn.Title];
}
else
{
string babyPhotoUrl = GetBabyPhotoUrl((int)workflowItem[siteColumn.Title], pubWeb.Web);
if (babyPhotoUrl != "")
{
string fieldName = siteColumn.Title.Replace("Photo", "Image");
ImageFieldValue babyPhotoImageFieldValue = cradleRollListItem[fieldName] as ImageFieldValue;
if (babyPhotoImageFieldValue == null)
{
babyPhotoImageFieldValue = new ImageFieldValue();
}
babyPhotoImageFieldValue.ImageUrl = rootWeb.ServerRelativeUrl + babyPhotoUrl;
cradleRollListItem[fieldName] = babyPhotoImageFieldValue;
}
}
}
}
This is a bigger chunk of code, so let's go through it a little at at time. First, we already know the title of the page, so let's set it. Secondly, we're going to iterate through the fields that are a part of the "Cradle Roll" content type to copy values from the InfoPath library to the Pages library, since we're using virtually the same columns from that content type in both places. We only want to copy the relevant fields, so to skip all those additional fields like "last modified by", etc., I'm only looking to copy those fields that start with words like "Baby", "Mother", and "Father".
If you remember correctly, we only stored the ID of the item in the Baby Photos image library with the InfoPath library list item. We need to execute some special code to look at the image library and retrieve the URL, so we can populate the Publishing Image field. Here's the private method we're calling to do that:
private string GetBabyPhotoUrl(int id, SPWeb web) {
try
{
string url = "";
if (id != 0)
{
SPList babyPhotos = web.Lists["Baby Photos"]; SPListItem babyPhotoItem = babyPhotos.GetItemById(id);
url = babyPhotoItem.File.Url;
}
return url;
}
catch
{
throw;
}
}
Last but not least, we're going to update our newly-modified page list item, and check it in.
cradleRollListItem.Update();
cradleRollListItem.File.CheckIn("Created by InfoPath Publishing Workflow.");
break;
Deploying the Solution
One of the virtues of using the Visual Studio template for workflows is that you can easily deploy the project. To do so, right click on your project, and click "Build" to build your project, then "Deploy" to deploy your project to the server.
If you didn't set up a workflow association when you first created your project, you can go to your Cradle Roll list and explicitly associate this workflow with that document library. To do so, go to the List Settings page for your Cradle Roll document library, and click on "Workflow Settings." If you click on "Add a workflow", you should now see "InfoPathPublishingWorkflow" listed under the other SharePointn workflows, as a kind of workflow you can create.

There you go! Give it a whirl! Try kicking off your workflow, and you'll see a new Page appear in your Pages library, which should have the information from the InfoPath form, along with the pictures you specified.
Congratulations! You're done!
Click here to download InfoPathPublishingWorkflow.cs as a text file.