I didn't forget about the promise I made, blogging about .NET 4.0 and VS 2010 new and spicy features, but before that, I need to blog about an interesting experience on developing a MSO word solution.
For starters, the discovery was when I found the Open XML SDK that allows me to manipulate my Word document without having office installed on the server running my application, which is a major breakthrough!
The code I used to add custom properties was taken from MSDN and I show it to you here:
public bool WDSetCustomProperty(string docName, string propertyName, object propertyValue, PropertyTypes propertyType) { const string documentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/" + "2006/relationships/officeDocument"; const string customPropertiesRelationshipType = "http://schemas.openxmlformats.org/officeDocument/" + "2006/relationships/custom-properties"; const string customPropertiesSchema = "http://schemas.openxmlformats.org/officeDocument/" + "2006/custom-properties"; const string customVTypesSchema = "http://schemas.openxmlformats.org/officeDocument/" + "2006/docPropsVTypes"; bool retVal = false; PackagePart documentPart = null; string propertyTypeName = "vt:lpwstr"; string propertyValueString = null; // Calculate the correct type. switch (propertyType) { case PropertyTypes.DateTime: propertyTypeName = "vt:filetime"; // Make sure you were passed a real date, // and if so, format in the correct way. The date/time // value passed in should represent a UTC date/time. if (propertyValue.GetType() == typeof(System.DateTime)) { propertyValueString = string.Format("{0:s}Z", Convert.ToDateTime(propertyValue)); } break; case PropertyTypes.NumberInteger: propertyTypeName = "vt:i4"; if (propertyValue.GetType() == typeof(System.Int32)) { propertyValueString = Convert.ToInt32(propertyValue).ToString(); } break; case PropertyTypes.NumberDouble: propertyTypeName = "vt:r8"; if (propertyValue.GetType() == typeof(System.Double)) { propertyValueString = Convert.ToDouble(propertyValue).ToString(); } break; case PropertyTypes.Text: propertyTypeName = "vt:lpwstr"; propertyValueString = Convert.ToString(propertyValue); break; case PropertyTypes.YesNo: propertyTypeName = "vt:bool"; if (propertyValue.GetType() == typeof(System.Boolean)) { // Must be lower case! propertyValueString = Convert.ToBoolean(propertyValue).ToString().ToLower(); } break; } if (propertyValueString == null) { // If the code cannot convert the // property to a valid value, throw an exception. throw new InvalidDataException("Invalid parameter value."); } using (Package wdPackage = Package.Open( docName, FileMode.Open, FileAccess.ReadWrite)) { // Get the main document part (document.xml). foreach (PackageRelationship relationship in wdPackage.GetRelationshipsByType(documentRelationshipType)) { Uri documentUri = PackUriHelper.ResolvePartUri( new Uri("/", UriKind.Relative), relationship.TargetUri); documentPart = wdPackage.GetPart(documentUri); // There is only one document. break; } // Work with the custom properties part. PackagePart customPropsPart = null; // Get the custom part (custom.xml). It may not exist. foreach (PackageRelationship relationship in wdPackage.GetRelationshipsByType( customPropertiesRelationshipType)) { Uri documentUri = PackUriHelper.ResolvePartUri( new Uri("/", UriKind.Relative), relationship.TargetUri); customPropsPart = wdPackage.GetPart(documentUri); // There is only one custom properties part, // if it exists at all. break; } // Manage namespaces to perform Xml XPath queries. NameTable nt = new NameTable(); XmlNamespaceManager nsManager = new XmlNamespaceManager(nt); nsManager.AddNamespace("d", customPropertiesSchema); nsManager.AddNamespace("vt", customVTypesSchema); Uri customPropsUri = new Uri("/docProps/custom.xml", UriKind.Relative); XmlDocument customPropsDoc = null; XmlNode rootNode = null; if (customPropsPart == null) { customPropsDoc = new XmlDocument(nt); // The part does not exist. Create it now. customPropsPart = wdPackage.CreatePart( customPropsUri, "application/vnd.openxmlformats-officedocument.custom-properties+xml"); // Set up the rudimentary custom part. rootNode = customPropsDoc. CreateElement("Properties", customPropertiesSchema); rootNode.Attributes.Append( customPropsDoc.CreateAttribute("xmlns:vt")); rootNode.Attributes["xmlns:vt"].Value = customVTypesSchema; customPropsDoc.AppendChild(rootNode); // Create the document's relationship to the // new custom properties part. wdPackage.CreateRelationship(customPropsUri, TargetMode.Internal, customPropertiesRelationshipType); } else { // Load the contents of the custom properties part // into an XML document. customPropsDoc = new XmlDocument(nt); customPropsDoc.Load(customPropsPart.GetStream()); rootNode = customPropsDoc.DocumentElement; } string searchString = string.Format("d:Properties/d:property[@name='{0}']", propertyName); XmlNode node = customPropsDoc.SelectSingleNode( searchString, nsManager); XmlNode valueNode = null; if (node != null) { // You found the node. Now check its type. if (node.HasChildNodes) { valueNode = node.ChildNodes[0]; if (valueNode != null) { string typeName = valueNode.Name; if (propertyTypeName == typeName) { // The types are the same. // Replace the value of the node. valueNode.InnerText = propertyValueString; // If the property existed, and its type // has not changed, you are finished. retVal = true; } else { // Types are different. Delete the node // and clear the node variable. node.ParentNode.RemoveChild(node); node = null; } } } } if (node == null) { string pidValue = "2"; XmlNode propertiesNode = customPropsDoc.DocumentElement; if (propertiesNode.HasChildNodes) { XmlNode lastNode = propertiesNode.LastChild; if (lastNode != null) { XmlAttribute pidAttr = lastNode.Attributes["pid"]; if (!(pidAttr == null)) { pidValue = pidAttr.Value; // Increment pidValue, so that the new property // gets a pid value one higher. This value should be // numeric, but it never hurt so to confirm. int value = 0; if (int.TryParse(pidValue, out value)) { pidValue = Convert.ToString(value + 1); } } } } node = customPropsDoc. CreateElement("property", customPropertiesSchema); node.Attributes.Append(customPropsDoc.CreateAttribute("name")); node.Attributes["name"].Value = propertyName; node.Attributes.Append(customPropsDoc.CreateAttribute("fmtid")); node.Attributes["fmtid"].Value = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; node.Attributes.Append(customPropsDoc.CreateAttribute("pid")); node.Attributes["pid"].Value = pidValue; valueNode = customPropsDoc. CreateElement(propertyTypeName, customVTypesSchema); valueNode.InnerText = propertyValueString; node.AppendChild(valueNode); rootNode.AppendChild(node); retVal = true; } // Save the properties XML back to its part. customPropsDoc.Save(customPropsPart. GetStream(FileMode.Create, FileAccess.Write)); } return retVal; }
The purpose of having this is to write some custom properties, for my Word Add-in to work properly, which is quite handy in most situations.
Hope this is as much value to you as it was to me!
Be back soon