Exercise 1: Automating the Finalization of an Open XML Document

In this exercise you will create a SharePoint event handler that will automatically mark an Open XML document as finalized when its status has changed to completed. This is a common practice in the final steps of a workflow or other process that is intended to move the document through a specific lifecycle.

Task 1 – Review the DocumentFinalization project

In this task, open the DocumentFinalization project that already contains a content type, site column and document library definition. Become familiar with the structure of the solution provided as a starting point.

  1. Open Visual Studio 2010 and open the project at %Office2010DeveloperTrainingKitPath%\Labs\OpenXML\Source\[language]\Starter\DocumentFinalization.sln
  2. Review the definition of the Publication content type
    1. In the Solution Explorer, open the Elements.xml file in the Publication folder
  3. Review the definition of the PublicationLibrary list template
    1. In the Solution Explorer, open the Schema.xml file
    2. Review the markup and see the ContentType node containing the reference to the Publication content type

Task 2 – Create and Register the event receiver

In this task, you will create a new event receiver and add markup to the content type to associate the event handler you created to the existing Publication content type.

  1. Add a new PublicationEventReceiver class to the Publication content type
    1. Right click the Publication item in the Solution Explorer and click Add -> Class
    2. Name the class PublicationEventReceiver.vb(in case of VB) & PublicationEventReceiver.vb(in case of C#) and click Add
  2. Add the markup to the content type Elements.xml file to connect the content type with the event handler
    1. In the Solution Explorer, open the Elements.xml file in the Publication folder
    2. At the end of the file, immediately following the close FieldRefs node, add the following markup

      Note:
      The line breaks in the bold lines below exist to make the line fit on the printed page. When placing them into Visual Studio 2010, make sure there are no line feeds and the entire value is on one line.

      XML

      <XmlDocuments>
      <XmlDocument NamespaceURI="https://schemas.microsoft.com/ sharepoint/events"> <spe:Receivers xmlns:spe="https://schemas.microsoft.com/sharepoint/ events"> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class>FakePre-fa92ce1a22e24e1a8d742308ffd2add0-7e08d370a4ba4102a9cc930c4df7bc12FakePre-fdbc2e097b7347b9ba1969c2af7be3b1-85f34a49bf1b4f0aa6b68ed2da3f001bFakePre-f24f7fe1e4fd47c7bbf524f177dfcb09-ed3aac6c03c546c7a6c13a5197d999d5FakePre-cbb606e92c7047fab2f8595acd6d521e-4910fe34782644c4a21199a07df7ef27FakePre-5f61fa84dbd94e9191886ef118036618-4de2c1901e4245fa8fa5d46a6859169c <XmlDocument NamespaceURI="https://schemas.microsoft.com/ sharepoint/events"> <spe:Receivers xmlns:spe="https://schemas.microsoft.com/sharepoint/ events"> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class>FakePre-0555ec9459e94198949f086a3585d46a-441b50985b284f96aab814c4f5a007fdFakePre-3440a1fe053f46d580eeb0b71d97cdf6-1bc9a4ae69b14d769fbb6141fd201b18FakePre-d266e90dd03a4cb0959f1f5b9a2bb408-1230f8eb1a4f45cd862dd8ac21977b40FakePre-da2ff19a1ab8410b8d1fdd975fff0976-75ef61b7fcee4d8fa2767cff1157917aFakePre-f0a58d1b51e44638aa4e8b1cfe077538-80c09081e46a4775b5a31bdc0a469217FakePre-be4a54db5835496199058bfd205bc0b4-d4a3dc902dae4caca2402e7e0da93d13FakePre-ae05f538aec0410c88bff761e542c570-c9d2073ff63c4d538d6a202a272c45afFakePre-6ef69c4ca740469ea72926a67b407807-1e77e163538742288dbe6203ef603434 <XmlDocument NamespaceURI="https://schemas.microsoft.com/ sharepoint/events"> <spe:Receivers xmlns:spe="https://schemas.microsoft.com/sharepoint/ events"> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class> <Class>DocumentFinalization.Publication. PublicationEventReceiver</Class>FakePre-4567e9fd08684567879968e943ba6ee1-d1e6a6c499ba412e9c743166571a13c8FakePre-6a674c3176354c028219a5bb3e46834d-270e32b418ad4785b08074f7d244baf9FakePre-426c5d86b61f42afa80caa55e2af0757-c4a6aec5378a41ea85d115044c9e177dFakePre-5e34413cca0b4d938475bf9b25ea1e45-1295ffbc8ecb438bbc49c237dca6ac50FakePre-17a4f83cdeb2436bb599623152eebd5e-866182a61f1b47baa56c1b8eda8851aaFakePre-1aa73d113b90439381a0b16362c2e795-eaf53cac07b140e2a3cfffe35ae6c923

  3. Implement the SPItemEventReceiver class
    1. Add the following using statements to the new PublicationEventReceiver.vb(in case of VB) & PublicationEventReceiver.vb(in case of C#)

      C#

      using Microsoft.SharePoint;

      Visual Basic

      Imports Microsoft.SharePoint

    2. Update the class definition so PublicationEventReceiver derives from SPItemEventReceiver and that the class is public

      C#

      public class PublicationEventReceiver
      : SPItemEventReceiverFakePre-7d2b69fb30014bd2b46c7d394e4fa76c-1cb3c0b326ea44ea94f588e341ee8a31

      Visual Basic

      Public Class PublicationEventReceiver Inherits SPItemEventReceiver

  4. Add a TryFinalizeDocument method to call the finalization code if the PublicationStatus of the document is Published
    1. Add a PublicationStatus static field to the PublicationEventReceiver class

      C#

      public class PublicationEventReceiver
      FakePre-d6e22e4e42c8423ebe0bedfcf38b9b28-eeb3dfcbfff043a2bf6ae6e52dedec86FakePre-461ebbab5e9a4b6780b9420cefe544a7-3295e43c69624912aa4031a0bf18a84f static Guid PublicationStatus = new Guid("{31B7D719-0D60-470B-B637-D71507CA20B5}");

      Visual Basic

      Public Class PublicationEventReceiver
      FakePre-c8278311172a4850904822730020be94-381b1b636aac4d26997b0d5d00393020Private Shared PublicationStatus As New Guid("{31B7D719-0D60-470B-B637-D71507CA20B5}")

    2. Create a new method TryFinalizeDocument

      C#

      void TryFinalizeDocument(SPItemEventProperties properties) { }

      Visual Basic

      Private Sub TryFinalizeDocument(ByVal properties As SPItemEventProperties) End Sub

    3. Use the properties.ListItem object to check the value of the PublicationStatus field in the TryFinalizeDocument method

      C#

      string status = (string)properties.ListItem[PublicationStatus]; if (status == "Published") FinalizeDocument(properties.ListItem);

      Visual Basic

      Dim status As String = CStr(properties.ListItem(PublicationStatus)) If status = "Published" Then FinalizeDocument(properties.ListItem) End If

    4. Override the ItemAdded and ItemUpdated methods and have them call TryFinalizeDocument

      C#

      public override void ItemAdded(SPItemEventProperties properties) { TryFinalizeDocument(properties); } public override void ItemUpdated(SPItemEventProperties properties) { TryFinalizeDocument(properties); }

      Visual Basic

      Public Overrides Sub ItemAdded(ByVal properties As SPItemEventProperties) TryFinalizeDocument(properties) End Sub Public Overrides Sub ItemUpdated(ByVal properties As SPItemEventProperties) TryFinalizeDocument(properties) End Sub

Task 3 – Open and Save the file in SharePoint

In this task, you will add code to the event handler that opens a file in SharePoint and manipulates its contents.

  1. Add a reference to the Open XML SDK and add the necessary namespaces to the event receiver
    1. In the Solution Explorer, right click DocumentFinalization and choose Add Reference
    2. Locate DocumentFormat.OpenXml in the .NET tab, select it, and click OK
    3. Add the following using statements to PublicationEventReceiver.vb(in case of VB) & PublicationEventReceiver.vb(in case of C#)

      C#

      using System.Xml; using System.Xml.Linq; using DocumentFormat.OpenXml.Packaging; using System.IO;

      Visual Basic

      Imports System.Xml Imports System.Xml.Linq Imports DocumentFormat.OpenXml.Packaging Imports System.IO

    4. Add two OpenXML namespaces to the PublicationEventReceiver as static fields

      Note:
      The line breaks in the bold lines below exist to make the line fit on the printed page. When placing them into Visual Studio 2010, make sure there are no line feeds and the entire value is on one line.

      C#

      public class PublicationEventReceiver
      FakePre-acc7592f4b18438bbc192b14fd1d39bc-448f801fa7664d449c88ee1ca9ff2495FakePre-a93ae54909b344b4896f1fbf9126a313-a51aab3be6e848d380452aa8a8e608baFakePre-9a9e7e30072b490cbd32f3f3c2923f99-b569e00076c2438fa329cff986db60feFakePre-2086b5b1e8b44679bb0cad97a1200ab9-968da937675c4d3bad4a47603c2a38d1static XNamespace XMLNS_CP = "https://schemas.openxmlformats.org/officeDocument /2006/custom-properties"; static XNamespace XMLNS_VT = "https://schemas.openxmlformats.org/officeDocument /2006/docPropsVTypes";

      Visual Basic

      Public Class PublicationEventReceiver
      FakePre-3f16918c7be54cb9b851de68afaefd78-b44d5b3b349b40a7889b0f60615be49cFakePre-ecbd0ea8e07446d9b814d584cedad896-31e19c7a29c34d44a0da9c15e1ec7b8ePrivate Shared XMLNS_CP As XNamespace = "https://schemas.openxmlformats.org/officeDocument /2006/custom-properties" Private Shared XMLNS_VT As XNamespace = "https://schemas.openxmlformats.org/officeDocument /2006/docPropsVTypes"

  2. Implement the FinalizeDocument method
    1. Create a new FinalizeDocument method

      C#

      void FinalizeDocument(SPListItem item) { }

      Visual Basic

      Private Sub FinalizeDocument(ByVal item As SPListItem) End Sub

    2. Use the following code in the FinalizeDocument method to guarantee our changes to the document don’t cause infinite loops

      C#

      try { EventFiringEnabled = false; } finally { EventFiringEnabled = false; }

      Visual Basic

      Try EventFiringEnabled = False Finally EventFiringEnabled = False End Try

  3. Locate the updated file and load it using the Open XML SDK
    1. Inside the try block following the EventFiringEnable line, create a new MemoryStream to hold the file
    2. Use the stream.Write and the item.File property to load the file into the stream
    3. Load the stream using the WordprocessingDocument and call the MarkAsFinal method
    4. Use the SPFile object’s SaveBinary method to save the changes back to SharePoint

      C#

      EventFiringEnabled = false;
      FakePre-42edbe7211b74dc2aae765e533f1c57d-f35c8c2c4bbb48a798fb5d2147f9af12using (MemoryStream stream = new MemoryStream()) { stream.Write(item.File.OpenBinary(), 0, (int)item.File.Length); using (WordprocessingDocument document = WordprocessingDocument.Open(stream, true)) MarkAsFinal(document); item.File.SaveBinary(stream); }FakePre-5e515cc7b7d54d3fb1554d5acaaf12e2-f418285e39fb435dbd9e1ead522b7406FakePre-8d2610ec13aa4ff68004b9c59a704423-3e70f2cbcce3456b95e6dae5e73fd2f6

      Visual Basic

      EventFiringEnabled = False
      FakePre-0cf1491c25954389b5822162b4d05f9a-75b3406987054ed39935cf048e1a35bbUsing stream As New MemoryStream() stream.Write(item.File.OpenBinary(), 0, CInt(Fix(item.File.Length))) Using document As WordprocessingDocument = WordprocessingDocument.Open(stream, True) MarkAsFinal(document) End Using item.File.SaveBinary(stream) End UsingFakePre-aea82fe27f604641b3c52d68b8c85583-4b19aab0c2d64d518a552687b053c66fFakePre-e78bae101756459eb86a48c3ae6571e7-d88303153ed64389bc0c7a67adfcbdad

Task 4 – Mark the document as finalized

In this task, you will use the Open XML SDK to access the _MarkAsFinal property in the document and set it to true. If it does not exist, you will create it.

  1. Implement the MarkAsFinal method that will use the WordprocessingdDocument object to access the custom file properties and set the _MarkAsFinal property to true
    1. Create a new MarkAsFinal method

      C#

      static void MarkAsFinal(WordprocessingDocument document) { }

      Visual Basic

      Shared Sub MarkAsFinal(ByVal document As WordprocessingDocument) End Sub

    2. Steps b-e build up the code in the MarkAsFinal method. Use the document parameter to retrieve the CustomFilePropertiesPart object

      C#

      CustomFilePropertiesPart propertiesPart = document.GetPartsOfType<CustomFilePropertiesPart>().First();

      Visual Basic

      Dim propertiesPart As CustomFilePropertiesPart = document.GetPartsOfType(Of CustomFilePropertiesPart)().First()

    3. Load the contents of the part into an XDocument

      C#

      XDocument properties = null; using (XmlReader reader = XmlReader.Create(propertiesPart.GetStream())) properties = XDocument.Load(reader);

      Visual Basic

      Dim properties As XDocument = Nothing Using reader As XmlReader = XmlReader.Create(propertiesPart.GetStream()) properties = XDocument.Load(reader) End Using

    4. Call the SetPropertyValue method to set the _MarkAsFinal property to true

      C#

      SetPropertyValue(properties, "_MarkAsFinal", true);

      Visual Basic

      SetPropertyValue(properties, "_MarkAsFinal", True)

    5. Write the contents of the XDocument back into the CustomFilePropertiesPart

      C#

      using (XmlWriter writer = XmlWriter.Create( propertiesPart.GetStream(FileMode.Create, FileAccess.ReadWrite))) properties.WriteTo(writer);

      Visual Basic

      Using writer As XmlWriter = XmlWriter.Create(propertiesPart.GetStream(FileMode.Create, FileAccess.ReadWrite)) properties.WriteTo(writer) End Using

  2. Use XLinq to find the _MarkAsFinal property. If it exists, set it to true. If not create it and set it to true.
    1. Create a new SetPropertyValue method

      C#

      static void SetPropertyValue(XDocument properties, string propertyName, bool value) { }

      Visual Basic

      Shared Sub SetPropertyValue(ByVal properties As XDocument, ByVal propertyName As String, ByVal value As Boolean) End Sub

    2. Steps b-d build up the code in the SetPropertyValue method. Use the properties parameter to find the property with a specific name

      C#

      XElement property = properties.Descendants(XMLNS_CP + "property") .FirstOrDefault(xe => xe.Attribute("name").Value == propertyName);

      Visual Basic

      Dim [property] As XElement = properties.Descendants(XMLNS_CP & "property").FirstOrDefault(Function(xe) xe.Attribute("name").Value = propertyName)

    3. If the property found is null, create a new property. To find the id of the new property find the max current property id and add 1

      C#

      if (property == null) { int maxPropertyId = properties.Descendants(XMLNS_CP + "property") .Select(xe => Int32.Parse(xe.Attribute("pid").Value)).Max(); property = new XElement(XMLNS_CP + "property", new XAttribute("fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"), new XAttribute("pid", maxPropertyId + 1), new XAttribute("name", "_MarkAsFinal")); properties.Root.Add(property); }

      Visual Basic

      If property Is Nothing Then Dim maxPropertyId As Integer = properties.Descendants(XMLNS_CP & "property").Select(Function(xe) Int32.Parse(xe.Attribute("pid").Value)).Max() property = New XElement(XMLNS_CP & "property", New XAttribute("fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"), New XAttribute("pid", maxPropertyId + 1), New XAttribute("name", "_MarkAsFinal")) properties.Root.Add(property) End If

    4. Whether the property was found initially or added, set its value to true

      C#

      property.SetElementValue(XMLNS_VT + "bool", value);

      Visual Basic

      property.SetElementValue(XMLNS_VT & "bool", value)

  3. Deploy the solution by right clicking DocumentFinalization in the Solution Explorer and clicking Deploy

Exercise 1 Verification

In order to verify that you have correctly performed all steps of exercise 1, proceed as follows:

Test the Event Handler

Test your event handler you will need to create a new document library in the SharePoint site based on the Publication Library template. Now add a document to the document library and set its Publication Status to Published. This will cause the content type event handler to execute and mark the document as finalized.

  1. Create a new document library named Shared Documents using the Publication Library template
    1. In Internet Explorer navigate to https://intranet.contoso.com/sites/OpenXML
    2. Click Site Actions and View AllSite Content link on the left hand side navigation menu
    3. At the top of the page click the Create button

      Figure 1

      Create Share Documents Library

    4. In the Create dialog, filter by Library and choose the Publication Library option
    5. Enter a name of Shared Documents and click Create
  2. Add a new document to the Shared Documents document library
    1. In the Documents ribbon click the New Document button

      Figure 2

      Create New Document

    2. When Word 2010 loads save the document to the new Shared Document library
    3. Close Word 2010
    4. Back in Internet Explorer, refresh the page
    5. Click the drop down menu on the new document and select Edit Properties

      Figure 3

      Editing Item Properties

    6. Change the Publish Status to Published and click Save

      Figure 4

      Change the Publication Status

    7. Click the drop down menu on the new document and select Edit in Microsoft Word
    8. Once the document is loaded verify it has been finalized

      Figure 5

      Finalized Word Document