Chapter 6. Merging SharePoint List Datainto Word Documents

Organizations often have sets of document templates that are used throughout the enterprise. It can be a challenge to make sure that information workers are uniformly using the latest template and capturing the appropriate metadata everywhere the document templates are used. It is not unusual for some templates to share common data elements such as customer information or product details. Often authors, who are responsible for working with these templates, are retyping, cutting and pasting, or otherwise repetitively importing data elements into the appropriate places in the document. Furthermore, they may use a completely separate application to locate and maintain the data set. In this chapter, we will describe how, as a developer, you can construct a solution that uses a SharePoint site to enable users to merge list data into Microsoft Word documents.

Real-World Examples

Users have been looking for ways to automate data merge functionality since the beginning of word processing. It is, in essence, a different take on a mail merge. In this case, the data source is a SharePoint list. In more and more companies these days, workers are relying on SharePoint lists in place of local spreadsheets or Access databases. Users can create a custom list with custom fields using just a browser, making it easier than ever to build these repositories without relying on a developer. Add to this the fact that the list is Web-based and allows other users' to edit individual items, and the lure is undeniable. The scenario is so generic that it could apply to almost any organization in any industry. If the list involves customer contact information, the documents templates could be contracts, statements of work, and letters. If the list comprises task assignments, the document templates could be work orders, summary sheets, and status updates. If the list holds product information, the document templates could include order forms.

Solution Overview

For this solution, we will develop a feature that, when enabled in a site, provides the functionality to merge list-item data into specific organizational document templates. SharePoint already provides functionality similar to this, with quick parts that can bind areas of the document to its properties. However, this is limited to a document's properties, which means you can't pull in data from a separate SharePoint list, only from properties of the document in its library. In this solution, we will utilize a completely different list in the site. To provide some context for our work, we focused our efforts on building a solution that deals with an organization's customers. Once the feature has been enabled in a site, the solution will create a Customer Contacts list along with a Customer Documents document library. The document library will reference the organization's document templates, which will be configured as SharePoint content types. The Customer Contacts list will be enhanced with an additional action integrated into that list's ribbon interface, so that for a specific contact, a user may select to build a customer document. After the selection, the user will be directed to a custom application page that enables him to select which specific type of customer document he would like to be constructed with the contact's data. In this example, we will build a business fax template, a customer follow-up template, and a template to thank new customers. Once the user has selected a document type, the application page will merge the contact's data with the document using the Open XML file format, and store the resulting file in a site library.

This solution should be flexible enough to allow any site administrator to simply turn on this functionality for the site. For this reason, we have developed the solution as a SharePoint feature. Features are a way of packaging a set of customizations as a single unit that can be activated or deactivated. With Visual Studio 2010, features take just a little effort to set up, and they increase the reusability of the solution and ease deployment complexities. (See the section "Building the Customer Documents Feature Project" later in this chapter for more detail.)

Solution Walkthrough

This section details the major elements of the solution and the decisions that were made in coding it. The walkthrough will show you how to create content types for the organization's document templates. We will describe how to package your solution as a feature that can be enabled by any site administrator who desires the functionality. This includes how to set up a feature project in Visual Studio, the XML files used to define the feature's customizations, and how to deploy it. The walkthrough also includes the construction of a custom SharePoint application page. Finally, the contact data and the document come together through the use of custom XML parts and Microsoft Word 2010 content controls.

Creating Content Types

In the scenario for this solution, an organization has a set of document templates that are often used to communicate with customers. These document templates and their required metadata should be used uniformly throughout the enterprise. We want to define the settings for each document type once and have any site or even any library in the site collection be able to reference them. For this reason, we will explain how to define each as a SharePoint content type.

Content types allow organizations to reuse settings that apply to a particular category of content. These settings include metadata requirements, a template file, workflows, and policies. Once the content type is defined for the site collection, any library can be configured to support the content type. In SharePoint 2010, you can also set up publish-subscribe relationships for the content type definitions to be replicated across site collections. This technique is actually used in Chapter 11's solution, in case you are interested. Enabling a content type for a library results in an additional selection being added to the library's New menu, as shown in Figure 6-1.

Content types in a library's New menu

Figure 6-1. Content types in a library's New menu

Interestingly, the metadata columns associated with the library and the content type do not have to match. This allows users to upload different files of different content types with different metadata to the same document library. This flexibility breaks down the barriers that required administrators to create multiple libraries in older versions of SharePoint. It allows you to store different types of documents that should be grouped based on their user or security requirements. It also provides a single point of administration, as changes to the content type need to be made in only one place. Content types also support inheritance, where a derived type can reuse and extend its parent's settings.

To view the content types of a site collection, navigate to the Site Settings administration area from the top site in the collection. There you'll find the Site Content Types gallery. Figure 6-2 displays the gallery, including the Customer Metadata Model group and its content types, which you will create using the steps in the remainder of this section.

Site Content Type gallery page

Figure 6-2. Site Content Type gallery page

By browsing through some of the default content types, you can see that they include settings for columns, workflow, information panels, and information management policies. Each content type also has an associated template that can be set from its Advanced Settings page. The documents used in our example will be a business fax, a customer follow-up, and a thank-you letter for new customers. Though these document templates are different, they are all related by a common, overlapping core of metadata requirements for the organization. So in defining them, we will first create a content type called CustomerDocument that will contain all of the common settings and serve as a parent for the others. Follow these steps to create the CustomerDocument content type:

  1. Click the Create toolbar button on the Site Content Type Gallery page.

  2. Enter CustomerDocument as the content type name.

  3. Since all of our types are documents, select Document Content Types and then Document to identify the parent content type.

  4. Use the radio button to create a new group named Customer Metadata Model and click OK.

Notice that this content type automatically inherits the Name and Title columns due to its child relationship with the Document content type. All of these columns are SharePoint site columns. Like content types, site columns provide an element of reuse as they uniformly define a column for a site collection. For this reason, content types must use site columns. Follow these steps to extend the CustomerDocument content type to include a Company metadata column:

  1. Since Company is a site column that already exists, select Add from Existing Site Columns.

  2. Locate the Company site column in Core Contact and Calendar Columns.

  3. Click the Add button.

  4. Click OK.

  5. Click on the Company column in the list and make it a required field.

  6. Click OK.

Now repeat the content-type creation steps (the first numbered list) for the BusinessFax, FollowUpCustomer, and ThankYouNewCustomer types, except select the CustomerDocument content type as the parent. Notice that each gains the Company site column in its settings. Add a few different columns for each so you can explore how the library changes to accommodate the various schemas. Also, upload a Word 2010 document to serve as a template from the content type's Advanced Settings page. We will modify the templates later, but for now use the Business Fax Cover Sheet, Follow Up to Prospective Customer, and Thank You to New Customer templates from Microsoft Office Online. The Business Fax Cover Sheet template can be found in the Faxes group of Microsoft Office Online's templates and the other two are in the Letters group in the Business category. Make sure that when you save them, you choose to use the Office 2010 file format, resulting in a .docx file extension. You can also get these files in the code download for this chapter. The Word documents are stored in a folder named Extras.

To test your content types, you will want to associate them with a document library. This library must be in the same site collection, but not necessarily the same site. Pick any test document library and follow these steps to enable the content types:

  1. Navigate to the library's Settings page using the Library Settings button in the Library tab of the ribbon.

  2. Select Advanced Settings from the General Settings group.

  3. Use the radio buttons to enable the management of content types.

  4. Click OK.

  5. The library's Settings page should have a new section called Content Types. Use the Add from Existing Content Types link to associate our content types: BusinessFax, FollowUpCustomer, and ThankYouNewCustomer. The result should look like Figure 6-3.

A document library with content types listed

Figure 6-3. A document library with content types listed

Now create at least one instance of each document type and save them to the document library. Do not enter any new content into the files, but complete the metadata entry form. Later we will use these documents to explore their XML structures, modify them to hold custom data, and upload them as new templates for the content types.

Building the Customer Documents Feature Project

Our solution will provide the organization with a set of elements that together enable any site in the site collection to maintain a list of customer contacts, a customer document library, and the ability to merge contact data into specific document templates. The important point here is that we want to provide multiple instances of this capability throughout the site collection. Some sites may want to leverage it while others may not. For this reason, we will package our solution as a SharePoint feature.

A SharePoint feature is a deployable unit that packages site customizations and custom code into a single component that an administrator can activate or deactivate. Features have a defined scope that dictates the breadth of their influence. A feature can be scoped to the entire server farm, a single web application, a site collection, or just a site (web). This scope determines the level of administrator needed to turn it on or off and the level at which its customizations are applied. Features can include customizations such as list templates, list instances, or custom menu items, and can be used to provision new files such as master-page templates, web part definitions, and web-content-management page layouts. Features can even run custom code in response to their activation or deactivation. Figure 6-4 displays the Manage Site Features administration page from the Site Settings of a team site we will use in this chapter.

Site features of a team site

Figure 6-4. Site features of a team site

To understand the impact of a feature, explore what happens when you deactivate the Team Collaboration Lists feature. You will receive a warning; deactivate it. Turning off this feature removes your ability to create instances of most of the list types in your site. In fact, this option will not even show up in any creation dialog. If you reactivate the feature, the options return. In case you are wondering, deactivating this feature does not delete any existing lists that have already been created. But that was a choice by the developers who created this feature. You will have to decide what you want to happen when your feature is activated and deactivated on a site.

To create the feature, we will use a new SharePoint Empty Project in Visual Studio 2010. Name the project and solution CustomerDocumentsFeature. Use the following steps to create the project:

  1. Open Visual Studio 2010 and start a new project.

  2. In the Installed Templates tree, expand Visual C#. Expand SharePoint. Select the 2010 node for the SharePoint 2010 project types.

  3. Using the drop-downs above the project types, make sure you are targeting the .NET Framework 3.5.

  4. Select Empty SharePoint Project from the list of project types.

  5. Name the project CustomerDocumentsFeature and leave the default choices of creating a new solution with the same name. Your completed dialog should look like Figure 6-5.

    Creating the CustomerDocumentsFeature project

    Figure 6-5. Creating the CustomerDocumentsFeature project

  6. Click OK.

  7. In the next SharePoint Customization dialog, choose the Deploy as a farm solution option, since our solution is really an enterprise one that would be built by developers, deployed into production by system administrators, and used across multiple site collections. Specify the URL of the site where you created the content types. For our environment it was http://edhild3/sites/chapter6.

Defining the Feature

Your project is currently just an empty SharePoint project and needs the definition of the feature that a site administrator will activate to add customizations to the site. Specifically, our customizations will result in the creation of a contact list called CustomerContacts and a document library named CustomerDocuments. We will make sure that the content types we just created are automatically attached to the document library. We will include a custom ribbon element for contact items that enables the user to indicate she would like to merge the selected contact with one of our document templates. Clicking this button will take the user to a custom applications page where we will perform the merge and return the resulting document. But let's not get too far ahead of ourselves. Fortunately, Visual Studio 2010 provides SharePoint developers with tools to define such a feature. Use the following steps to create the feature that will bundle all of these customizations.

  1. Right-click on the Features node in Solution Explorer and choose to Add Feature. This will create a new SharePoint feature named Feature1 and open its Visual Studio designer.

  2. Close the designer. Right-click on Feature1 and rename the feature to CustomerDocumentsFeature.

  3. Now open the designer again. Make sure the Title of the feature is CustomerDocumentsFeature. Set the Description value to Adds a custom action to contact list items to merge a contact item into a Word document. Lastly, set the scope to Web as all of our customizations will be scoped to a particular SharePoint site.

The next part of this section will specify the customizations that we want to occur when our feature is activated. Each of these will be specified in an element manifest. An element manifest contains the settings of the customizations that the feature will contain. You can have any number of manifests, but the Visual Studio Project Items will add one for each type of item you add to the Visual Studio solution. Let's first focus on making our feature create the CustomerContacts list.

  1. Right-click on the project and choose to Add a New Item.

  2. In the Add New Item dialog, choose List Instance and name the item CustomerContacts.

  3. The dialog will contact the site you selected when you created the Visual Studio solution, to see what list templates are available. It is important to realize that we are not creating the list in the site, but rather specifying that this feature should create such a list automatically when it is activated. Figure 6-6 shows the customization wizard with the settings for the title of the list (CustomerContacts), the list template type (Contacts), and the URL (Lists/CustomerContacts), as well as the option to have the list automatically placed on the quick launch navigation bar of the site.

Adding the CustomerContact list instance to the feature

Figure 6-6. Adding the CustomerContact list instance to the feature

We will take a brief moment here to appreciate what Visual Studio is doing for us under the hood. Remember, the feature is the overarching umbrella that will reference each of our customizations. The feature is defined by an XML file feature.xml. Customizations are stored in element manifests usually named elements.xml. If you expand the CustomerContacts node, you can see this file and the XML that defines the customization shown in Listing 6-1.

Example 6-1. Creating the Customer Contacts List

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ListInstance Title="CustomerContacts"
                OnQuickLaunch="TRUE"
                TemplateType="105"
                FeatureId="00bfea71-7e6d-4186-9ba8-c047ac750105"
                Url="Lists/CustomerContacts"
                Description="">
  </ListInstance>
</Elements>

It is fairly straightforward to decode what is going on in the elements file. Listing 6-1 includes a ListInstance element that tells the feature that upon activation a new list should be added to the site. The element includes the title of the list, that the list should be on the quick launch navigation bar, and the desired URL. The TemplateType attribute equates to our choice that we wanted an instance of SharePoint's contacts list. The FeatureId GUID specifies the internal SharePoint feature where the contacts list definition is contained. It's a good thing that Visual Studio finds these GUIDs for us as part of the wizard. Otherwise, you'd have to hunt through the Features directory of the 14 hive (C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14TEMPLATEFEATURES) to find it in a feature.xml file.

Now repeat the same steps you used to create the CustomerContacts list instance for the CustomerDocuments document library. Be sure to name the library CustomerDocuments, set its URL to Lists/CustomerDocuments, and make sure you are creating an instance of the Document Library template.

Now let's turn our attention to adding a new button to the ribbon of contact items in our site. Like our other customizations, this too will be contained in its own element manifest. Unfortunately, there is no project item that will automatically write the XML necessary to get our button in the right spot. Instead, use the following steps to add an empty element manifest that we will code ourselves.

  1. Right-click on the project and choose to Add a New Item.

  2. In the Add New Item dialog, choose the Empty Item SharePoint project item template and name the item CustomerDocumentsElements.

Expand the node to find the element manifest file where we will place the XML markup to tell SharePoint to add a button for contact items in the site. The next few code listings will detail all the XML you'll need to place in the file. If you are not inclined to type all this in yourself, just grab the corresponding file from the code download. Listing 6-2 kicks off the element manifest by defining the custom action. The Id attribute is a string that uses a naming convention of specifying not only a name but also the location the button is to be placed. This doesn't actually determine location (we will see that later), but it's a good practice when naming your custom actions. In this case, we want the BuildCustomerDocuments action to make a customization to the actions group for list items. The RegistrationType attribute indicates an attachment type. In this case, the action is associated with a list. Other possible attachment types include ContentType, FileType, or ProgId. The RegistrationId attribute further clarifies the attachment by specifying the identifier of the list, item, or content type that the attachment is associated with. In this case, the value of 105 represents the list template ID matching this action with contact lists.

Example 6-2. Defining the Build Customer Document custom action

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="Ribbon.ListItem.Actions.BuildCustomerDocuments"
                Location="CommandUI.Ribbon"
                RegistrationId="105"
                RegistrationType="List"
                Title="Build Customer Document">

The next section of the element manifest file defines the user interface extension and, in particular, the button we would like to place in the actions group for contact items (see Listing 6-3). Here the Location attribute of the CommandUIDefinition element is the real factor in determining where our ribbon customization will go. Notice that we are specifying that our extension should be added to the controls collection of the actions group for list items. You may wonder how we figured out the correct name. The good news here is that most of the out-of-the-box ribbon interface is stored in the CMDUI.XML file in the 14 hive. By default that file is located at C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14TEMPLATEGLOBALXML. You can learn a lot from inspecting this file and the different type of techniques used to set up the interface. The rest of the user interface definition specifies our button, the images to use depending on the button's size, and a label. The sequence number determines the order the button will be placed within the group. TemplateAlias is a value that controls the layout of the graphic and label on the ribbon. We found this value by looking at the CMDUI.XML file for a similar layout where the label is directly below the image. Lastly, a command attribute specifies a specific handler that should be called when the user clicks the button. We will cover that in the next listing. Figure 6-7 shows what the end result will look like.

Example 6-3. Defining the Build Customer Doc Button

<CommandUIExtension>
  <CommandUIDefinitions>
    <CommandUIDefinition
       Location="Ribbon.ListItem.Actions.Controls._children">
      <Button Id="Ribbon.ListItem.Actions.BuildCustomerDocumentsButton"
              Image16by16="/_layouts/images/DOC16.gif"
              Image32by32="/_layouts/images/DOC32.gif"
              LabelText="Build Customer Doc"
              Sequence="100"
              TemplateAlias="o1"
              Command="HelloCommand" />
      </CommandUIDefinition>
    </CommandUIDefinitions>
The deployed Build Customer Doc button

Figure 6-7. The deployed Build Customer Doc button

The last section of the element manifest file, shown in Listing 6-4, specifies the command handler that dictates what should happen when the user clicks the button. In this case, we want the user to be redirected to a custom application page named BuildCustomerDoc.aspx that we will add to the feature later. Fortunately, SharePoint's JavaScript libraries already have a GoToPage method defined for such a task. In addition to redirecting, we want to make sure we pass some key information to that custom application page. Mainly we want to pass the specific contact's identifier along with its list identifier as querystring parameters. Notice that the URL used in the XML has placeholder tokens that SharePoint will automatically fill in. There is a bit of Javscript work being done here to retrieve the identifier of the selected contact. In this solution, we only expect the user to select a single contact since that is all our documents are set up to support. We are also using the SiteUrl placeholder so that our custom application page can be surfaced regardless of what site the user happens to be on. The SelectedItemId placeholder tells our application page which contact item the user had selected before clicking the button. The ListId placeholder passes the identifier of the list containing the selected item. You can read up on more URL tokens and custom actions at http://msdn.microsoft.com/en-us/library/ms473643(office.14).aspx.

Example 6-4. The Command Handler for the Build Customer Doc Button

<CommandUIHandlers>
  <CommandUIHandler
     Command="HelloCommand"
     CommandAction="javascript:GoToPage('{SiteUrl}/_layouts/
The Command Handler for the Build Customer Doc Button
CustomerDocumentsFeature/BuildCustomerDoc.aspx?
The Command Handler for the Build Customer Doc Button
List={ListId}&amp;ID={SelectedItemId}'),"/> </CommandUIHandlers> </CommandUIExtension> </CustomAction> </Elements>

The last customization we want to include in this section is binding the content types we created earlier to the CustomerDocuments library. In this case, these content types are already defined in the site collection, so we are simply binding them to our library. If you wanted to see how you could include the content types in the feature itself, check out the solution in Chapter 14. Defining a binding can be done by adding the XML in Listing 6-5 to the CustomerDocuments elements file just after the definition of the ListInstance element.

Example 6-5. Binding Content Types to the Document Library

<ContentTypeBinding
      ContentTypeId="0x010100AA8705BBBC554342BC1396AB7FB723B401"
      ListUrl="Lists/CustomerDocuments"
/>
<ContentTypeBinding
      ContentTypeId="0x010100AA8705BBBC554342BC1396AB7FB723B402"
      ListUrl="Lists/CustomerDocuments"
/>
<ContentTypeBinding
      ContentTypeId="0x010100AA8705BBBC554342BC1396AB7FB723B403"
      ListUrl="Lists/CustomerDocuments"
 />

This addition contains three ContentTypeBinding elements that are responsible for linking the content types we created earlier with the Customer Documents document library. Now the ContentTypeIds in Listing 6-5 will be specific to your environment since you created them in the earlier step. Each of these bindings maps to one of the CustomerDocument content types we created earlier: BusinessFax, FollowUpCustomer, and ThankYouNewCustomer. Looking at the ContentTypeIds in Listing 6-5, it is easy to tell that they all share a common parent. The telling sign is that the ID is exactly the same up to the last digit. In SharePoint, when a content type is derived from another type, it simply extends the ID by a few characters. In addition, the fact that each of these types begins with 0x0101 tells us that an ancestor of this type is the base Document content type. As Figure 6-8 shows, you can obtain the content type IDs in your environment by copying the ctype querystring parameter on the Site Content Type administration page. Use this technique to retrieve each of your ContentTypeIds and update the code in Listing 6-5 to match your environment.

Obtaining the Content Type ID

Figure 6-8. Obtaining the Content Type ID

Just to wrap up our behind-the-scenes thread, go back and pull up the designer for the CustomerDocumentsFeature feature. You'll notice that each of our element files are referenced as items in the feature. This designer interface is shown in Figure 6-9. If you click the Manifest button at the bottom of the designer, you'll see the feature.xml file that Visual Studio built for us.

The feature in Visual Studio's Feature Designer

Figure 6-9. The feature in Visual Studio's Feature Designer

Deploying the Feature

Even though we are not completely done with the feature (it's missing the custom application page), you have enough that is worth deploying and testing on your team site. To deploy the feature, just right-click on the project and choose Deploy. This will package the solution up and place your files in the correct locations on the SharePoint server. It will also activate your feature, which should create the contacts list and document library, along with binding the content types and incorporating the new ribbon button. Take some time to click through and verify that all of these customizations are working. Of course, if you click the new button in the ribbon, you'll get an error. We will resolve that in just a bit. As a note, if you redeploy this project to the team site, you may see the dialog that is shown in Figure 6-10. Even though Visual Studio is deactivating the existing feature as part of the deployment, we have not written any code to actually delete the list and library instances. Clicking the Resolve Automatically button will perform those deletions for you as part of your debugging process, and you can have Visual Studio remember this choice by using the Do not prompt me again check box.

Resolving deployment conflicts

Figure 6-10. Resolving deployment conflicts

Building a Custom Application Page

When a user selects the Build Customer Document action from a contact item in the site, the solution will redirect the user to an application page that will show the document templates that are available from the Customer Documents document library. After the user makes a selection, the page will serialize the contact list item and place it in the Word document template as a custom XML part. The next section of this chapter goes into further detail about Microsoft Word's support for custom XML parts. We will focus on creating the page, laying out its controls, and the initial processing. Figure 6-11 depicts the end goal for BuildCustomerDoc.aspx (the Build Customer Documents page).

The Build Customer Documents custom application page

Figure 6-11. The Build Customer Documents custom application page

The Build Customer Documents page is termed an application page because it is not part of a site definition, but rather lives in SharePoint's Layouts directory. This means that the ASPX page can be accessed from the path of any site through the _layouts virtual folder. Application pages are ASP.NET pages that derive from a SharePoint class LayoutsPageBase. Fortunately, Visual Studio supports adding a custom application page as a project item. Use the following steps to get an empty application page into the CustomerDocumentsFeature project.

  1. Right-click on the project and choose to Add a New Item.

  2. In the Add New Item dialog, choose the Application Page project item template and name the item BuildCustomerDoc.aspx.

After the addition of the application page completes, you'll notice that a few things have changed in your project. First, a Layouts folder has been added. This is a SharePoint mapped folder, meaning that it has a corresponding location in the 14 hive. This is Visual Studio's way of organizing where things need to be deployed when it creates the deployment package. Within the Layouts folder, there is a folder that is the name of your project. This folder is created in the server's Layouts folder directly in an effort to avoid name collisions with other application pages that may have been created for different solutions. Lastly, the requested BuildCustomerDoc.aspx is in that folder. If you view the markup of the application page, you will see several content placeholders. The last two are just for the title, so place the text Build Customer Doc there. The focus of this section will be on the Main content placeholder, as that's where our user interface needs to be defined for the page. Instead of dragging all the controls yourself or retyping the markup of the server controls, copy and paste what you need from the code download—it's just a table with a drop-down a list control, a textbox, a label, and two buttons. The ASPX markup of the Main placeholder is shown in Listing 6-6.

Example 6-6. Content of the Build Customer Documents Application Page

<table border="1" cellpadding="5" cellspacing="0"
       style="width:100%; font-size: 9pt" >
<tr>
    <td>Document Type:</td>
    <td><asp:DropDownList ID="lstContentTypes"  runat="server"
                          EnableViewState="true"/></td>
</tr>
<tr>
    <td>New File Name:</td>
    <td><asp:TextBox ID="txtFileName"  runat="server"
                     EnableViewState="true"/></td>
</tr>
<tr>
    <td></td>
    <td><asp:Button ID="btnCancel"  Text="Return" runat="server" />&nbsp;
        <asp:Button ID="btnOK" Text="Generate" runat="server" /></td>
</tr>
</table>
<asp:Label ID="lblMessage" runat="server" EnableViewState="true"
  Text="Your new Document has been saved to the CustomerDocuments library" />

This application page would be shown in response to a user clicking on the Build Customer Document action from any site that has the feature activated. For this reason, the page must first establish its context and get references to the site collection and the site from which it is being invoked. With these pieces of information, the application page can use the querystring parameters that were passed by the custom action to identify the contacts list the user was in when he selected the action, as well as the individual item he selected. Listing 6-7 includes the code for these initial operations, which you can find in the code-behind file BuildCustomerDoc.aspx.cs. We are not including every line of code in the text (like using statements), so please use the code download.

Example 6-7. Initializations of the Application Page

SPSite siteCollection = null;
SPWeb webObj = null;
Guid listId = Guid.Empty;
int itemId = 0;
SPFolder customerDocLib = null;

protected void Page_Load(object sender, EventArgs e)
{
 try
 {
   siteCollection = SPContext.Current.Site;
   webObj = SPContext.Current.Web;
   listId = new Guid(Server.UrlDecode(Request.QueryString["List"]));
   itemId = int.Parse(Request.QueryString["ID"]);
   customerDocLib = webObj.GetFolder("Lists/CustomerDocuments");

During the page-load event, several of the server controls on the page are initialized. The label is hidden, the event handler for the Generate button is configured, and the Cancel button is set up to return the user to the previous page where the custom action was selected. The drop-down list is populated with the content types that are bound to the site's Customer Documents document library. We exclude the default Document content type since it is not a custom one that will support the contact's data as a custom XML part. Listing 6-8 includes the code necessary to set up the server controls.

Example 6-8. Setting Up the Server Controls

lblMessage.Visible = false;
//build button event handler
btnOK.Click += new EventHandler(btnOK_Click);
if (! this.IsPostBack)
{
  string returnUrl = "javascript:GoToPage('{0}'),return false;";
  this.btnCancel.OnClientClick = string.Format(returnUrl,
                                 Request.QueryString["Source"]);
  IList<SPContentType> contentTypes = customerDocLib.ContentTypeOrder;
  //populate the drop down
  int i = 0;
  foreach (SPContentType contType in contentTypes)
  {
    if (contType.Name != "Document")
    {
      ListItem item = new ListItem(contType.Name, i.ToString());
      lstContentTypes.Items.Add(item);
    }
    i++;
  }
}

Using a Custom XML Part in a Document Template

The custom application page will merge the selected contact item with the chosen document template. To facilitate this operation, we will leverage the fact that the document templates are Microsoft Word files saved using the new Open XML file format. As explained in Chapter 4, the Microsoft Office desktop tools have switched from their proprietary binary-formatted files to formats based on Open XML specifications. Now each file—whether spreadsheet, presentation, or document—is really a package of parts and items. Parts are pieces of content for the file, whereas items are metadata describing how the parts should be assembled and rendered. Most of these pieces are XML files, making it possible for them to be manipulated through code. You can gain insight into the structure of an Open XML–based file by replacing its file extension with .zip, since the file is really an ordinary Zip archive. We will use an instance of the business-fax template we created earlier. Make sure you are modifying a BusinessFax document that has been saved to the site's Customer Documents document library once before. This will help you understand how SharePoint uses the Open XML file formats, as well. Figure 6-12 shows the root of the archive for the Business Fax document.

Examining the archive of a Word document

Figure 6-12. Examining the archive of a Word document

The XML file in the root is named [Content_Types].xml and it stores content-type directives for all the parts that appear in the archive. A content type contains metadata about a particular part or groups of parts and, more importantly, contains a directive as to how the application should render that part. For example, Listing 6-9 shows just a few lines from the XML file, but clearly delineates how this file tells the rendering application which parts are images, styles, relationships, custom properties, and even the main document.

Example 6-9. The Document's Content Types

<Override PartName="/customXml/itemProps1.xml"
   ContentType="application/vnd.openxmlformats-officedocument.
The Document's Content Types
customXmlProperties+xml" /> <Default Extension="wmf" ContentType="image/x-wmf" /> <Default Extension="rels" ContentType="application/vnd.openxmlformats-
The Document's Content Types
package.relationships+xml" /> <Default Extension="xml" ContentType="application/xml" /> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.
The Document's Content Types
document.main+xml" /> <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.
The Document's Content Types
styles+xml" />

Pay particular attention to the first line of Listing 6-9. The Override element is describing the itemProps1.xml file as being something more than just any XML file inside the document package. In fact, itemProps1.xml is a file that contains the properties of a custom XML part. It describes item1.xml, which stores the actual custom XML data. All of the Office document types support having a custom XML folder that is capable of storing information. In fact, SharePoint itself creates three custom XML parts in Office files when they are uploaded into site libraries. These XML parts are for storing metadata values, the schema of the document information panel, and workflow information. Though all of the Office files can store custom XML, Microsoft Word offers the unique added benefit of having a set of content controls that can bind to the custom XML parts, displaying the data inline with the document. So open the BusinessFax document you created earlier and follow these steps to add content controls that we will bind to contact data:

  1. Delete the text following the Fax Number: prompt.

  2. Click on the Developer tab in the Microsoft Word ribbon. This tab may be hidden on your machine, but it can be enabled through the backstage. Click the File menu and then choose Options. In the left-hand navigation choose Customize Ribbon. Check the Developer group in the right-hand list and click OK.

  3. Place your cursor after the colon (:) and click the plain-text content control button Aa in the Controls group.

  4. Click the Properties button in the ribbon on the right-hand side of the Controls group.

  5. Set the Title and Tag values to FaxNumber.

  6. Click the check box to keep the content control from being deleted accidentally.

  7. Click the check box to disable editing of the control's content. The end result should look like Figure 6-13.

    Adding content controls to a document

    Figure 6-13. Adding content controls to a document

Repeat the same steps to add a plain-text content control for the To field. Name the content control FullName. The document could have many more content controls, but these two are enough to illustrate the solution. In this example, we are relying on just the plain-text content control, but Microsoft Word does support others, such as picture, rich text, drop-down list, and a calendar control for dates. Next we will add an additional custom XML part with contact data, and bind these controls to specific parts of the XML data. It used to be that you had to do all this manually in the XML parts, which included creating several property files, specifying XPath queries for the controls, and repackaging everything back up. Fortunately, there is now a toolkit that does most of the heavy lifting for you: The Word Content Control Toolkit. Download this utility from http://www.codeplex.com/dbe and install it into your environment. Use the following steps to get the new custom XML part with contact data into the document and bindings to the content controls setup.

  1. Launch the Word Content Control Toolkit.

  2. Open the BusinessFax document you placed the content controls on.

  3. In the bottom right-hand corner, click the Create a new XML Part link.

  4. Click the Edit View tab in the right-hand pane and place the contents of Listing 6-10 in the text area. This is just a portion of the fields that SharePoint can give us about a contact, but it's enough to bind the controls we added to the BusinessFax template. Notice that we took the time to declare our own namespace: http://www.sample.com/2006/schemas/contact/. This will be useful in determining which custom XML part is ours when we perform the merge in the next section.

    Example 6-10. The Contact Custom XML Part

    <?xml version="1.0"?>
    <sc:Contact xmlns:sc="http://www.sample.com/2006/schemas/contact/">
      <sc:ID>1</sc:ID>
      <sc:Last_x0020_Name>hild</sc:Last_x0020_Name>
      <sc:First_x0020_Name>ed</sc:First_x0020_Name>
      <sc:Full_x0020_Name>ed hild</sc:Full_x0020_Name>
      <sc:E-mail_x0020_Address/>
      <sc:Company>sample inc</sc:Company>
      <sc:Fax_x0020_Number>333-333-333</sc:Fax_x0020_Number>
      <sc:Address/>
      <sc:City/>
      <sc:State_x002F_Province/>
      <sc:ZIP_x002F_Postal_x0020_Code/>
    </sc:Contact>
  5. Click the Bind View tab in the right-hand pane.

  6. Click the Full_x0020_Name node. It will become highlighted as a result of the selection.

  7. Now use the left mouse button and drag the highlighted node to the FullName content control listed in the left hand side of the tool. As you'll see, the toolkit will write the corresponding XPath expression for the control, which will bind it to this data element of our custom XML part.

  8. Repeat the previous step for the FaxNumber node. The result should look like Figure 6-14.

  9. Click the Save button to commit the new custom XML part and content control bindings to the document. If you open this file again in Microsoft Word, you should see that the data from the custom XML part is surfaced through the content controls. This modified file then needs to be re-uploaded as the document template for the BusinessFax content type.

Binding content controls to the Custom XML part

Figure 6-14. Binding content controls to the Custom XML part

Performing the Merge

The BuildCustomerDoc.aspx application page has the responsibility of serializing the contact list item and placing it in the custom XML part we created in the preceding section. All of this work happens when the user clicks the Generate button on the custom application page (shown in Figure 6-11) to generate the document. Before looking at the OnClick event handler, there are a few other assemblies we need to add references to:

  • WindowsBase: This assembly gives us access to the System.IO.Packaging namespace, which is used to open up Office files that use the Open XML file format.

  • DocumentFormat.OpenXml: This assembly is provided by the Open XML Format SDK 2.0. For this chapter we are using the March 2010 release, which you can download and install from: http://msdn.microsoft.com/en-us/library/bb448854(office.14).aspx. This assembly lets us manipulate the content of the presentation without having to write XML nodes directly. The SDK provides an object model that is an abstraction from the actual XML, making our code easier to read and write.

Be sure to grab the appropriate using statements for the code-behind from the code download. The button's OnClick event handler locates the document template for the selected content type and copies it to a memory stream. This way the copy can be modified without changing the content type's template. Listing 6-11 includes this code.

Example 6-11. Copying the Content Type's Document Template

Stream docStream = new MemoryStream();
SPContentType contentType = customerDocLib.ContentTypeOrder[int.Parse
Copying the Content Type's Document Template
(lstContentTypes.SelectedValue)]; SPFile templateFile = contentType.ResourceFolder.Files
Copying the Content Type's Document Template
[contentType.DocumentTemplate]; Stream templateStream = templateFile.OpenBinaryStream(); BinaryReader reader = new BinaryReader(templateStream); BinaryWriter writer = new BinaryWriter(docStream); writer.Write(reader.ReadBytes((int)templateStream.Length)); writer.Flush(); reader.Close(); templateStream.Dispose();

The next step is to locate the custom XML part where the application page needs to place the content data. Unfortunately, this is not as easy as it may seem. We can't be guaranteed that the custom XML part we created earlier is still named item4.xml (which is what the Word Content Control named it). SharePoint may have reordered the parts when we uploaded the document to the system. Therefore, the code in Listing 6-12 opens the stream as a Word Document package and iterates through the custom XML parts, looking for the namespace (http://www.sample.com/2006/schemas/contact/) used to represent the contact data.

Example 6-12. Finding the Contact Data Custom XML Part

//open .docx file in memory stream as package file
docStream.Position = 0;
WordprocessingDocument wordDoc = WordprocessingDocument.Open(docStream, true);
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
//retrieve package part with XML data
XmlDocument xDoc = null;
CustomXmlPart customPart = null;
foreach(CustomXmlPart cp in mainPart.CustomXmlParts)
{
  xDoc = new XmlDocument();
  xDoc.Load(cp.GetStream());
  if (xDoc.DocumentElement.NamespaceURI ==
           "http://www.sample.com/2006/schemas/contact/")
  {
    customPart = cp;
    break;
  }
}

With the custom XML part located, the code continues. We first clear the custom XML part of any previous data (our sample data, for example) by calling the RemoveAll() method. The next step is to serialize the selected contact into an XML representation and store the result in the custom XML part. Listing 6-13 includes the code that performs this operation. Notice the use of the XmlConvert class to make sure that the column names are properly escaped for XML.

Example 6-13. Serializing the Contact to the Custom XML Part

SPListItem contactItem = null;
contactItem = webObj.Lists[this.listId].GetItemById(this.itemId);
//serialize the contact item into this customXml part
XmlNode rootNode = xDoc.DocumentElement;
rootNode.RemoveAll();
foreach (SPField field in contactItem.Fields)
{
  XmlNode fieldNode = xDoc.CreateElement("sc",
                      XmlConvert.EncodeName(field.Title),
                      "http://www.sample.com/2006/schemas/contact/");
  if (contactItem[field.Id] != null)
  {
    XmlNode fieldVal = xDoc.CreateTextNode(contactItem[field.Id].ToString());
    fieldNode.AppendChild(fieldVal);
  }
  rootNode.AppendChild(fieldNode);
}
xDoc.Save(customPart.GetStream(FileMode.Create, FileAccess.Write));

At this point, the memory stream contains the properly constructed document with the selected content data injected into the custom XML part. The only remaining task is to save the modified file back to the CustomerDocuments library with the file name requested by the user. Listing 6-14 contains this code. Note that we are assuming the user gives us a unique name for the file. If he chooses an existing name, we will overwrite it, which would generate a new version of the file if versioning were turned on. You'd probably want to add more safety to this save operation in a production environment.

Example 6-14. Saving the File Back to the Document Library

//deliver file to library
customerDocLib.Files.Add(txtFileName.Text, docStream);
lblMessage.Visible = true;

With the custom application page complete and the document templates updated with their content controls and custom XML parts, redeploy the feature. Place a contact in the Customer Contacts list and run the merge by selecting the Build Customer Document action. Choose the BusinessFax content type and generate the merged document.

Important Lessons

This chapter incorporated several key techniques that are worth highlighting as they can easily be reused in other projects.

Defining content types:

The solution detailed how to define the settings associated with a type of content for an organization. By creating a SharePoint content type, administrators have a single definition that can be reused uniformly throughout the site collection.

Developing a feature:

The solution was packaged as a SharePoint feature. This enables any administrator in the site collection to activate or deactivate the functionality included in it. This technique increases the flexibility of the solution and centralizes the steps needed to deploy it.

Custom application page:

The feature included a custom application page that was deployed to the server's Layouts directory. The page was created using the Visual Studio project item template and was integrated seamlessly to extend the site's functionality.

Open XML file format:

The manipulation of the Microsoft Word documents on the server was made possible by the fact that the templates were stored using the Open XML file format. The format's support for custom XML parts enabled us to insert data into the file without requiring Word to be installed on the server. The Open XML Format SDK streamlined the code we needed to write in order to navigate and manipulate the file.

Microsoft Word content controls:

Inserting the data into a custom XML part would have done little if it were not for Microsoft Word's content controls. These controls can be placed into a document and bound to the custom XML part through an XPath query. We automated this step using the Word Content Control Toolkit. Though we used them in a read-only manner in this chapter, if a user were able to change the value in the control, the control would update the custom XML part.

Extending SharePoint 2010's Ribbon:

SharePoint 2010 enables you to declaratively extend its ribbon interface. In this solution, we added a new button for when the user is focused on a contact item in the site.

Extension Points

While coding this example, we thought of several variations to the solution that we didn't incorporate. Mostly, these were not included because they distracted from the overall objective of the solution. We call them out now as extension points since they may be applicable to a specific project you are working on.

Delete a list and a library on feature deactivation:

The solution created the CustomerContacts list and CustomerDocuments library in the feature. You could use the FeatureDeactivating event of a feature receiver to find the CustomerDocuments library and CustomerContacts list and delete them. We don't see this as an automatic action every feature should take, since the user could very well lose some important data. Every feature is unique and you'll want to consider the cleanup activities carefully.

Set company property in the custom XML part:

In our example, the contact data was serialized into the custom XML part. However, all of the content types used in the example expected a Company value to be specified as a metadata property since the Company site column was added to the parent content type. We also mentioned that SharePoint uses the custom XML parts for data, including the file's metadata. Use the same technique to set the Company property before it is saved to the library.

Add context sensitivity to the ribbon extension:

In our example the ribbon button is enabled even if the user has yet to select a contact item. We included a link in the Further Reading section of this chapter that details how you can wire up some JavaScript events to enable and disable the button accordingly.

Configure the content types as a feature:

In the solution, the content types are created by interacting with the site, but they could have been made a feature. They could not, however, be in the same feature as the rest of the solution since content types can't be declared inside a feature of web-level scope (usually site collection). You would need a completely separate feature. Of course, you would then want to set an activation dependency between them.

Further Reading

The following links are to resources we think a reader interested in the material presented in this chapter would find useful:

  • Adding Custom Button to the SharePoint 2010 Ribbon http://blogs.msdn.com/jfrost/archive/2009/11/06/adding-custom-button-to-the-sharepoint-2010-ribbon.aspx

  • Word Content Control Toolkit http://www.codeplex.com/dbe

  • Creating a SharePoint 2010 Ribbon Extension - part 2 http://www.wictorwilen.se/Post/Creating-a-SharePoint-2010-Ribbon-extension-part-2.aspx

  • Default Server Ribbon Customization Locations http://msdn.microsoft.com/en-us/library/ee537543(office.14).aspx

  • Declarative Customization of the Ribbon http://msdn.microsoft.com/ibrary/ee534970(office.14).aspx

  • Brian Jones: Open XML Formats Blog http://blogs.msdn.com/brian_jones/

  • Intro to Word XML Part 1: Simple Word Document http://blogs.msdn.com/brian_jones/archive/2005/07/05/435772.aspx

  • Module 10: Creating Dialogs and Ribbon Controls for SharePoint 2010 video http://msdn.microsoft.com/en-us/sharepoint/ee513157.aspx

  • OpenXMLDeveloper.org http://openxmldeveloper.org/

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.207.70