Taking Advantage of Bound Content Controls

Happy New Year! I hope everyone had a good holiday. For my first post of the New Year I want to talk about content controls and how this technology provides true data/view separation in Wordprocessing documents. In my previous post, I showed you how to use content controls to provide semantic structure within documents. Today, I am going to show you how to go one step further by leveraging the ability of content controls to bind to custom xml.

Scenario – Generate Sales Contracts on the Server

Imagine a scenario where I'm a developer for a law firm that specializes in writing legal contracts for selling properties. My company uses the same exact template for all our property contracts. The only difference is the data/content contained within the contract. For example, who is selling the property, the address of the property, etc. My company has asked me to write a solution that allows lawyers to easily insert the necessary data into the template without needing to copy/paste the content within the document. An additional request is to have this solution generate the resulting document on the server.

Solution

I am going to base my solution on many of the concepts described in this excellent post from the Word team blog. My solution will take advantage of content controls that are bound to custom xml. In other words, by using bound content controls I will be separating the presentation of my documentation from my data, which will be stored in a separate custom xml part within my Wordprocessing document. Bound content controls allow for the following functionality:

  • When a user types into a bound content control, the corresponding data in the custom xml is updated appropriately
  • When the data in the custom xml part is updated, the corresponding content control content that is bound to that data is updated appropriately

To accommodate the server requirement I will build this solution on top of ASP.NET. My solution website will contain several form fields, which will represent the data to be inserted in the document. To easily insert this data into my document, I will generate a custom xml file based on this data and then insert this custom xml file into my Wordprocessing package. The content controls that are bound to this custom xml will automatically pull in the appropriate data.

If you just want to jump straight into the code, feel free to download this solution here.

Step 1 – Create a Template

As with my previous posts, the first step is always setting up the right template. In this case, my template will simply be the contract of sales with content controls around the regions in which necessary data needs to be inserted. My template will look like the following:

In addition to demarcating semantic regions, these content controls need to be bound to custom xml. The binding of a content control to an XML element within custom xml is accomplished by specifying the namespace of the custom xml file as well as the XPath expression which uniquely targets the element we wish to bind. Here is an example markup that specifies the binding of a content control:

<w:dataBinding w:prefixMappings="xmlns:ns0='https://contoso.com/2005/contracts/commercialSale' " w:xpath="/ns0:contract[1]/ns0:dateExecuted[1]" w:storeItemID="{ABB284D9-2C5E-41BD-A2F2-B5FC934955A9}"/>

Here are the three main ways to specify content control binding

  1. Content Control Tool Kit
    1. This addin to Word makes binding content controls to XML data easy and intuitive. A must have tool
  2. Use the Word object model
    1. You can use ContentControl.XMLMapping.SetMapping()
  3. Directly manipulate the underlying xml
    1. You can use the SDK to help out here

For my template I will simply bind my content controls to an empty custom xml file. In other words, this custom xml file contains no data. Binding to an empty custom xml file will ensure that just the placeholder texts of the content controls are shown.

Step 2 – Create the ASP.NET Front End Website

The next step is to create a front end website that allows users to insert data to be inserted into the document. For this step I created a simple ASP.NET site that looks like the following:

In the backend of the site I will take all this data and create a custom xml file that will be inserted into my Wordprocessing document.

Step 3 – Replacing Custom XML

Once I have created my custom xml file I now need to insert it into my Wordprocessing document. To accomplish this task I need to open my document and add this custom xml file as a custom xml part. The following code accomplishes these steps:

protected void ReplaceCustomXML(string fileName, string customXML) { using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileName, true)) { MainDocumentPart mainPart = wordDoc.MainDocumentPart; mainPart.DeleteParts<CustomXmlPart>(mainPart.CustomXmlParts); //Add a new customXML part and then add content CustomXmlPart customXmlPart = mainPart.AddNewPart<CustomXmlPart>(); //copy the XML into the new part... using (StreamWriter ts = new StreamWriter(customXmlPart.GetStream())) ts.Write(customXML); } }

Step 4 – Generating the Resulting Document

Once I have created the proper document with all the necessary information I need a way for the user to open or save the file. The following code accomplishes this task:

protected void GenerateContractButton_Click(object sender, EventArgs e) { string strTemp = Environment.GetEnvironmentVariable("temp"); string strFileName = String.Format("{0}\\{1}.dotx", strTemp, Guid.NewGuid().ToString()); File.Copy(Server.MapPath(@"App_Data/Contract of Sale.dotx"), strFileName); GetData(); string customXml = File.ReadAllText(Server.MapPath(@"App_Data/datatemp.xml")); ReplaceCustomXML(strFileName, customXml); //return it to the client - we know strFile is updated, so return it Response.ClearContent(); Response.ClearHeaders(); Response.AddHeader("content-disposition", "attachment; filename=Conract of Sale.dotx"); Response.ContentEncoding = System.Text.Encoding.UTF8; Response.TransmitFile(strFileName); Response.Flush(); Response.Close(); //Delete the temp file File.Delete(strFileName); File.Delete(Server.MapPath(@"App_Data/datatemp.xml")); }

At this point in time we have generated a document that has content controls bound to all the appropriate data. The resulting file will look like the following:

End Result

In this example scenario I only manipulated the parts within the Wordprocessing document. That means this code is fully functional with version 1 of the Open XML SDK.

Just to show how fast this solution is I attempted to create 100 documents on the server. This code took 1.166 seconds to generate 100 documents. Pretty cool!

Zeyad Rajabi