Share and Enjoy !

SharePoint Document Generation Introduction

I promise I won’t get off on a rant this time. Actually, I have decided to pattern my life after Bob Ross and be perpetually calm. The previous post described how the overall pattern we were establishing should enable flexible document generation. The interfaces and API were simplified to enable use from a variety of sources. Specifically, for most (not all) of our clients the source is SharePoint. But the basic document building blocks could be used from a console application, SharePoint, PowerShell or other environments. There are really three basic steps involved to build a document now using this pattern.

  • Creating a View (Document Template)
  • Loading the View from a Document Library
  • Building our Model from SharePoint Content
  • Generating and saving to a Document Library

Creating a View (Document Template)

To enable the view portion of our pattern, we need to create a template that serves as our view. In this case the view is a Word document that will serve as a template for our end documents. Using Content Controls we can mark up the document to include references to single elements, or build complex tables using Content Controls. Rather than rehash the process of adding Content Controls to Word documents, Erika Ehrli has a great starter article. As always, check out Eric White’s blog for other examples and information regarding Content Controls and Open Xml.

In the end, our Word document will contain Content Controls that enable mapping data to a content control(s), or that map rows and columns of data to a table or other structure for the Word document. Once we have this, we need to store this template in a known location. For most of our clients, we deploy these templates as documents to Document Libraries in SharePoint as part of a Feature or overall Solution Package.

Loading the View from a Document Library

Once we have a template marked up with the static content and the content placeholders, we can load this view into an IDocumentTemplate instance. From a UI perspective, we’ve had client’s ask to create documents from CustomActions on a SharePoint list items’ ECB menu, from a button on a web part that accepts other input data, and even from event handlers. The entry point may vary, but the next step is always the same. The code below shows how we obtain the template and turn this into an implementation of IDocumentTemplate.
SPDocumentLibrary templateDocumentLib = web.Lists["Templates"] as SPDocumentLibrary;
//build final path for the document, root folder, sub folder, etc.
//... (code elided)
string templatePath = site.RootWeb.Url + templateDocumentItem;
IDocumentTemplate documentTemplate =
new SharePointDocumentTemplate(SPContext.Current.Site.ID,
web.ID, new Uri(templatePath ),
site.RootWeb.GetFile(templatePath ).OpenBinary());

In this case, SharePointDocumentTemplate implements IDocumentTemplate. The call to GetFile(…).OpenBinary() returns a byte[] and enables access to the IDocumentTemplate.ToArray() method. We’ll use this byte[] to create a MemoryStream and process the template as a view. We now have our IDocumentTemplate to use in our call to WrodDocumentBuilder.BuildDocument(IDocumentTemplate documentTemplate, IDocumentModel documentModel) of our controller. Next, we need to build our model.

Building our Model from SharePoint Content

We have our view (IDocumenTemplate), and now we want to get that data that will populate the template. The following code contains the IModelBuilder interface definition and the call to an implementation of the interface to build a model.
public interface IModelBuilder
{
IDocumentModel GetDocumentModel();
}
//... (code elided)

 

//build the model for the document
IModelBuilder documentModelBuilder =
new StatusReportModelBuilder(SPContext.Current.Web,startDate,endDate,
includeChildWebs);
IDocumentModel documentModel = documentModelBuilder.GetDocumentModel();

What is most interesting about this call is how simple and powerful it is. Behind the scenes we use LOTS of LINQ and some Func<> delegates to make a very simple, flexible and extensible pattern to query data.

Review our description of IContentMapping and IContentMappingCollection from my previous rant. The content mappings are a source agnostic interface definition that lets us query variable sources, but they require transformation into the various IContentMapping implementations. The IQuery interface defines the way do this from any source, including SharePoint. IQuery, and the BaseQuery base class, enable us to set up base SharePoint List query functionality, which is then extended for specific scenarios like recursive Site queries, queries using SPSiteDataQuery, or just basic CAML queries, to return our content mappings in a model form we can use.
public interface IQuery

{
List
> Query();
IEnumerable QueryReturnListItems();
}

public abstract class BaseQuery : IQuery
{
public SPList ListToQuery { get; set; }

public Func Predicate { get; protected set; }
public string[] FieldNames { get; protected set; }

public BaseQuery(SPList listToQuery, string[] fieldNames)
{
ListToQuery = listToQuery;
FieldNames = fieldNames;
}

public abstract List
> Query();

 

//... (elided code)
}

In the end, our call to documentModelBuilder.GetDocumentModel(); returns us a fully fledged IDocumentModel.ContentMappings collection of data for all of our content in the template. This data may have come from any container in a site collection – single lists, aggregated lists or content types across lists and sites, etc. Neat, clean, and powerful!

Generating and saving to a Document Library

Great, we have our view, and our model, now let’s generate a document. Simply call the BuildDocument() method with our documentTemplate and documentModel and let the magic happen.
//Generate the document and get the byte[] back
byte[] newDoc = documentBuilder.BuildDocument(documentTemplate, documentModel);

 

//save the byte array to the folder
web.Files.Add(newDocPath, newDoc, web.CurrentUser,
web.CurrentUser, DateTime.Now, DateTime.Now);

Again, the previous post explains how the call to BuildDocument walks over the template and model and maps the content in the model to the content placeholders in the view. Finally, we take the byte[] of the new document and save this file to the web and path for the correct document library.

Conclusion

I would love to take credit for this, but much of the credit goes to Sean Hester for implementing an elegant and extensible document generation pattern that can be consumed from SharePoint and many other sources. Thanks Sean! Also, whenever I mention Open Xml, I always think of and thank Eric White for providing an incredible array of useful and informative SharePoint, LINQ and Open Xml blog posts.

Photo Credit – Bob Ross by Dave Nicoll, on Flickr

Share and Enjoy !

Related Content: