Working with Document Libraries

This book has already touched on several examples of developing and programming with documents and document libraries. At the end of Chapter 2, “SharePoint Architecture,” we showed you an example of adding a custom ECB menu item for documents in a document library that redirects the user to a custom application page that examines various properties of the current document. Chapter 3, “Pages and Design,” provided examples of programming against SPFile and SPFolder objects. Although the Chapter 3 examples dealt with .aspx pages that are not actually stored within document libraries, the programming techniques involving the SPFile and SPFolder classes are also valuable when working with documents stored within document libraries.

This chapter begins with an exploration of programming techniques involving the SPDocumentLibrary class, which gives you a better feel for designing and implementing document management solutions by using Microsoft Windows SharePoint Services (WSS). The chapter then examines the use of forms libraries and Windows SharePoint Services integration with Microsoft Office InfoPath 2007. At the end of this chapter, we will introduce the Office Open XML file formats, which provide techniques for generating, reading, and manipulating documents with server-side code in applications such as Microsoft Office Word 2007, Microsoft Office Excel 2007, and Microsoft Office PowerPoint 2007.


The majority of code samples in this chapter are based on a sample Microsoft Visual Studio project named DocumentManager that includes a feature named DocumentManager plus several custom application pages. If you want to follow along with the code examples here, you can open the DocumentManager project and build it, which copies all the relevant files to their proper locations in the TEMPLATES directory and installs the DocumentManager feature. You can then activate the feature in any site you want to test the code. And you can activate this feature from the Site Features page of any site.

SPDocumentLibrary Class

In terms of general WSS architecture, it’s important to observe that a document library is really just a specialized type of list. Similar to any standard list, you can add additional columns to a document library. This is a common practice in custom SharePoint solutions because it makes it possible to track metadata for documents. It is also important to note that each document library instance is included within the Lists collection of the current site.

 SPWeb site = SPContext.Current.Web; foreach (SPList list in site.Lists) {     // steps through all lists including document libraries }

The main difference between a document library and a standard WSS list is that a document library is designed to store documents instead of merely list items. The SPDocumentLibrary class is included in the WSS object model to provide programmatic control over the additional functionality that document libraries have over standard lists. Note that the SPDocumentLibrary class inherits from the SPList class. Once you obtain a reference to an SPList object, you can determine whether the list is also a document library by testing to see whether the SPList object is compatible with the SPDocumentLibrary type.

 public bool IsListAlsoDocumentLibrary(SPList list) {   if (list is SPDocumentLibrary)     return true;   else     return false; }

Assume that you want to write the code for a custom application page or a Web Part to populate a DropDownList control named lstTargetLibrary with the Title of the document library instances inside the current site. Keep in mind that there are many hidden document libraries that you might not want to display to users, such as the Web Part gallery, Site Template gallery, List Template gallery, and Master Page gallery. Therefore, you want to write your code to discover all of the document library instances for the current site that are not hidden.

 SPWeb site = SPContext.Current.Web; foreach (SPList list in site.Lists) {   if (list is SPDocumentLibrary && !list.Hidden) {     SPDocumentLibrary docLib = (SPDocumentLibrary)list;     // Add document library to DropDownList control     lstTargetLibrary.Items.Add(        new ListItem(docLib.Title, docLib.ID.ToString()));   } }

Note that the code shown here adds ListItem entries to the DropDownList control that includes the Title as the ListItem Text and the GUID-based ID as the ListItem Value. When a user selects a particular document library title from the DropDownList control, you can quickly access the target document library from the current site’s Lists collection by using its identifying GUID and converting it to an SPDocumentLibrary class.

 SPWeb site = SPContext.Current.Web; Guid libraryID = new Guid(lstTargetLibrary.SelectedValue); SPDocumentLibrary library = (SPDocumentLibrary)site.Lists[libraryID];

Programming Against Documents

There is a dual aspect to working with documents within a document library. Because every document library is represented in the WSS object model with an SPList object, every document in a document library has an associated SPListItem object. However, each document in a document library is also represented with an SPFile object, which means that you can program against a document in a document library as either an SPListItem or SPFile object. The following code presents an example of enumerating through a document library and creating both an SPList and SPFile object to program against each document.

 void ProcessDocuments(SPDocumentLibrary docLib) {   foreach (SPListItem item in docLib.Items) {     // program against item variable as SPListItem object     SPFile file = item.File;     // program against file as SPFile object   } }

As you can see, once you have the SPListItem object for a document, you can then retrieve the associated SPFile object by accessing the File property. The SPListItem object can be used to track a document’s ID and read or write metadata columns.

 foreach (SPListItem item in docLib.Items) {   // get document ID   int itemID = item.ID;   // read metadata column   string clientColumnValue = item["Client"].ToString();   // write metadata column   item["Client"] = "AdventureWorks";   item.Update(); }

The SPFile object, on the other hand, can be used to control other aspects of the document and to read or write the document’s content. Examine the following code fragment:

 foreach (SPListItem item in docLib.Items) {   if (item.FileSystemObjectType == SPFileSystemObjectType.File) {     SPFile file = item.File;     // check on number of versions     int versionCount = file.Versions.Count;     // determine when document was checked out     DateTime checkedOutDate = file.CheckedOutDate;     // open document for stream-based access     using(Stream fileContents = file.OpenBinaryStream()) {       // program against stream to access document content     }   } }

One thing you must watch for is the scenario in which a document library contains folders in addition to documents. Note that folders, like files, are stored as items within a document library and show up as SPListItem objects in the Items collection. This is why the previous code checks the FileSystemObjectType property of the current SPListItem object before attempting to process it as an SPFile object.

Discovering documents by enumerating through the Items collection of a document library finds all documents without regard for whether they exist within the root folder or within folders nested below the root folder. If you would rather enumerate through only the documents within the root folder of a document library, you can use a different approach, similar to what was demonstrated in Chapter 3 when using the SPFolder and SPFile classes.

 void ProcessDocumentsAtRoot(SPDocumentLibrary docLib) {   foreach (SPFile file in docLib.RootFolder.Files) {     // program against file using SPFile class   } }

If you want to write code in this fashion that continues to discover documents in nested folders, you can write code that involves recursion. In the DocumentManager project, a custom application page named DocumentManager1.aspx, which is accessible through the Site Settings menu, contains code to discover all documents in a site that exist within document libraries that are not hidden. The code populates a TreeView control displaying the document libraries as well as the hierarchy of the folder structure within each document library, as shown in Figure 7-1.

image from book
Figure 7-1: DocumentManager1.aspx contains code to populate an ASP.NET TreeView control with nodes for all documents within the current site.

The code used to populate the TreeView control in DocumentManager1.aspx is not overly complex. It contains an OnLoad event handler that enumerates through all of the lists and discovers which are document libraries that are not hidden. For each of these document libraries, it creates a TreeNode control and then calls a helper method named LoadFolderNodes to create child TreeNode controls for each document. The LoadFolderNodes method calls itself to move recursively through all folders that might be nested below the root folder.

  const string DOCLIB_IMG = @"\_layouts\images\ITDLSM.GIF"; const string FOLDER_IMG = @"\_layouts\images\FOLDER.GIF"; const string FILE_IMG = @"\_layouts\images\ICGEN.GIF"; protected override void OnLoad(EventArgs e) {   SPWeb site = SPContext.Current.Web;   foreach (SPList list in site.Lists) {     if (list is SPDocumentLibrary && !list.Hidden) {       SPDocumentLibrary docLib = (SPDocumentLibrary)list;       SPFolder folder = docLib.RootFolder;       TreeNode docLibNode = new TreeNode(docLib.Title,                                          docLib.DefaultViewUrl,                                          DOCLIB_IMG);       LoadFolderNodes(folder, docLibNode);       treeSitesFiles.Nodes.Add(docLibNode);     }   }   treeSitesFiles.ExpandDepth = 1; } protected void LoadFolderNodes(SPFolder folder, TreeNode folderNode) {   foreach (SPFolder childFolder in folder.SubFolders) {     if (childFolder.Name != "Forms") {       TreeNode childFolderNode = new TreeNode(childFolder.Name,                                               childFolder.Name,                                               FOLDER_IMG);       LoadFolderNodes(childFolder, childFolderNode);       folderNode.ChildNodes.Add(childFolderNode);     }   }   foreach (SPFile file in folder.Files) {     TreeNode fileNode;     fileNode = new TreeNode(file.Name, file.Name, FILE_IMG);     folderNode.ChildNodes.Add(fileNode);   } } 

Adding a New File to a Document Library

It is now time to write the code required to create a new document within a document library. If you want to follow along in the DocumentManager project, use Microsoft Visual Studio to open the custom application page named DocumentManager2.aspx and examine the code in the OnLoad event handler that populates a DropDownList control named lstTargetLibrary with all of the available document libraries in the current site. The page definition for DocumentManager2.aspx also contains a check box named chkShowHiddenLibraries that enables the user to determine whether the hidden libraries should be shown to users.

 SPWeb site = SPContext.Current.Web; if (!IsPostBack) {   lstTargetLibrary.Items.Clear();   foreach (SPList list in site.Lists) {     if ( (list is SPDocumentLibrary) &&        ( !list.Hidden || chkShowHiddenLibraries.Checked) ) {       SPDocumentLibrary docLib = (SPDocumentLibrary)list;       lstTargetLibrary.Items.Add(                  new ListItem(docLib.Title, docLib.ID.ToString()));     }   } }

Figure 7-2 displays the basic page layout for the custom application page named DocumentManager2.aspx. The user interface enables the user to select a target document library and type in a file name. It also provides a multiline TextBox control that enables the user to type in the content to be written into the new file. Although this example is fairly simple, it steps through what’s required to programmatically create a new document and save it within a document library.

image from book
Figure 7-2: DocumentManager2.aspx demonstrates a programmatic technique for creating new documents through the WSS object model.

When the user clicks the button with the caption Create Document, an event handler executes the code required to create a simple text file and store it within the target document library. The first issue you must deal with is parsing together the path for the new document. Examine the following code, which uses the ServerRelativeUrl property of a document library’s RootFolder object together with the MakeFullUrl method of the current SPSite object to construct the full path for a new document.

 SPSite siteCollection = SPContext.Current.Site; SPWeb site = SPContext.Current.Web; Guid libraryID = new Guid(lstTargetLibrary.SelectedValue); SPDocumentLibrary library = (SPDocumentLibrary)site.Lists[libraryID]; // parse together name of new document inside target library string documentName = txtFileName.Text; string libraryRelativePath = library.RootFolder.ServerRelativeUrl; string libraryPath = siteCollection.MakeFullUrl(libraryRelativePath); string documentPath = libraryPath + "/" + documentName;

As you can see, the document path is parsed together using the file name as well as a full path to the target document library. Once you have parsed together the path, it’s necessary to use a stream-based programming technique to write the contents of the new file. There are many possible ways to accomplish this by using various classes within the System.IO namespace. One efficient way to accomplish this task for a simple text file is by creating a MemoryStream object and writing into it by using a StreamWriter object.

 // create a memory stream and add document content Stream documentStream = new MemoryStream(); StreamWriter writer = new StreamWriter(documentStream); writer.Write(txtDocumentBody.Text); writer.Flush();

Once you have parsed together the path for the new document and written the new document’s content into a MemoryStream, you can then simply save the new document into the target document library by calling the Add method on the Files collection on the SPWeb object for the current site.

 // add document into document library site.Files.Add(documentPath, documentStream, true);

At first you might think this code to store a document in a document library is a bit counterintuitive because you are not calling an Add method on an object that represents the document library itself. The Add method is called on the Files collection of the current site, and nothing except the document path that you parsed together tells WSS which document library to target when storing the new document.

Now, consider a scenario in which you are adding a document to a document library that has custom metadata columns. For example, imagine that you are dealing with a document library with a custom column named Client that is used to associate each document with a particular client. What should you do if you want to assign a value to this column when you store the document?

An overloaded version of the Add method exists for scenarios when you are adding new documents to a document library that has custom metadata columns. Before calling this overloaded implementation of the Add method, you should create a new Hashtable object and add a name-value pair for each custom metadata column. For example, imagine that you want to save a document into a document library and assign a value to a custom metadata column named Client as well as the built-in Title column. You can accomplish this by using the following code:

 Hashtable docProperties = new Hashtable(); docProperties["Client"] = "AdventureWorks"; docProperties["vti_title"] = "AdventureWorks Sales Proposal"; site.Files.Add(documentPath, documentStream, docProperties, true);

Note that the property name of the Title column is vti_title and not Title, as you might expect. Many of the built-in columns used in WSS lists and document libraries have similar names that are not intuitive. You can discover these file property names by enumerating through the Properties collection of an SPFile object created from an object in the target document library.

 SPList list = site.Lists["Proposals"]; foreach (SPListItem item in list.Items) {   SPFile file = item.File;   foreach (DictionaryEntry entry in file.Properties) {     Console.WriteLine(entry.Key + ": " + entry.Value);   }   break; }

Using a Feature to Provision a New Document Library Instance

When you need to create a document library instance within a site for a business solution, you have several different options. You can create a custom document library type following the same steps that were outlined in Chapter 6, “Lists and Content Types,” for creating a custom list type. You can also create custom content types designed specifically for the types of documents with which you are dealing and then define a document library type that is based on those content types.

However, in a situation in which you just need to provision an instance of a standard document library, you can take a much simpler approach. For example, the DocumentManager feature contains a ListInstance element that automatically provisions an instance of the standard WSS document library type named Proposals on activation.

 <ListInstance   FeatureId=""   TemplateType="101"      Description="Document Library for proposals"   OnQuickLaunch="True"   Title="Proposals"   Url="Proposals" > </ListInstance>

When you use the ListInstance element to create an instance of the standard WSS document library type, the FeatureId attribute value must be assigned the GUID that identifies the standard WSS feature named DocumentLibrary, and the TemplateType attribute value must be assigned the type identifier for the standard document library type, which is 101. All other attributes are used to configure the new document library instance.

If you would like to preload some documents into this document library instance, you can place the physical document files into the feature directory and provision them into the document library by adding a Module element after the ListInstance element. For example, if your feature directory contains an inner directory named TestData containing standard documents, you can provision those documents into the document library instance on feature activation by adding the following Module element:

 <Module Name="TestData" List="101" Path="TestData" Url="Proposals" >   <File Url="Adventure Works Proposal.docx" Type="GhostableInLibrary" />   <File Url="Contoso Proposal.docx" Type="GhostableInLibrary" /> </Module>

Now that you have seen how to provision a document library instance within a feature, you likely want to replace the standard document template with one of your own. Quite a bit of work is involved if you want to add support for a custom document template using only Collaborative Application Markup Language (CAML) because CAML only enables you to define document templates in a site definition, something that involves far more work then necessary for this scenario. A much easier approach involves mixing declarative CAML logic with some WSS object model code in the Feature_Activated event.

You should start by using CAML to provision the custom document template file into the target site by using a Module element. When doing this, it is recommended that you place the document template within the Forms folder of the target document library.

 <Module Name="WordTemplate" List="101" Url="Proposals/Forms">   <File Url="ProposalTemplate.dotx" Type="GhostableInLibrary" /> </Module>

Next, you can create a FeatureActivated event handler and add the code required to create an SPDocumentLibrary object associated with the target document library. You can then assign a path to the document template to the DocumentTemplateUrl property. Make sure you remember to call the Update method to save your changes.

 public override void FeatureActivated(                           SPFeatureReceiverProperties properties) {   SPWeb site = (SPWeb)properties.Feature.Parent;   SPDocumentLibrary libProposals;   libProposals = (SPDocumentLibrary)site.Lists["Proposals"];   string templateUrl = @"Proposals/Forms/ProposalTemplate.dotx";   libProposals.DocumentTemplateUrl = templateUrl;   libProposals.Update(); }

When you obtain an SPDocumentLibrary reference to the newly created document library, you can do far more than simply assign a custom document template. You can also configure many other aspects of the document library’s behavior, such as disabling folder creation, enabling versioning, and enabling forced document checkout.

 libProposals.EnableFolderCreation = false; libProposals.EnableVersioning = true; libProposals.ForceCheckout = true; libProposals.Update();

Creating a Custom Application Page for Document Management

As you are designing and implementing document libraries for a custom business solution, you might want to give users the ability to perform custom operations on the documents inside them. By creating CustomAction elements within a feature, it’s easy to add custom menu items to the ECB menu for documents within a document library. In Chapter 2, you viewed an example of the addition of a CustomAction based on the list type for Document Libraries, which is 101.

 <CustomAction    RegistrationType="List"   Registration   <!–other attributes omitted for clarity --> />

While this action adds an ECB menu item for each document in a standard document library, it is not guaranteed to add this ECB menu for all documents. For example, there could be specialized types of document libraries, such as forms libraries (shown later in this chapter) that have a different list type ID.

A second option when creating a CustomAction is to specify the target item in terms of its file extension. This provides the ability to supply a custom ECB menu item for a special type of file, for example, those with .doc, .docx, and .pdf extensions.

 <CustomAction    RegistrationType="FileType"   Registration   <!–other attributes omitted for clarity --> />

A third option when creating a CustomAction is to specify the target item in terms of its content type, which provides more flexibility because content types support inheritance. When you specify a content type, it automatically includes all of the other content types that inherit from it. For example, if you wanted to add an ECB menu item for every type of item, including documents, you can use the generic Item content type with a RegistrationId of 0x01.

 <CustomAction    RegistrationType="ContentType"   Registration   <!–other attributes omitted for clarity --> />

In the DocumentManager feature, a CustomAction element is provided to create a custom ECB menu item for any type of document. Therefore, it has been created in terms of the generic Document content type. The full CustomAction element looks like this:

 <CustomAction    RegistrationType="ContentType"   Registration   ImageUrl="/_layouts/images/GORTL.GIF"   Location="EditControlBlock"   Sequence="120"   Title="Document Manager Assistance" >   <UrlAction Url="~site/_layouts/Litware/              DocumentManager3.aspx?ItemId={ItemId}&amp;ListId={ListId}"/> </CustomAction>

Note that in this book the string value of the Url attribute of the inner UrlAction element is broken into two lines for readability. However, when you create a CustomAction element in an actual source file, make sure the string value for the Url attribute does not include line breaks.

Once you activate the DocumentManager feature within a site, every document within any document library receives a new ECB menu item with a title of Document Manager Assistance, as shown in Figure 7-3. When a user selects this ECB menu item for a specific document, WSS responds by redirecting the user to the URL specified in the UrlAction element. As discussed in Chapter 2, WSS dynamically replaces the tokens~site, {ItemId}, and {ListId} when it generates this URL at run time. This resulting URL is site specific and redirects the user to the custom application page named DocumentManager3.aspx with a QueryString indentifying the document and its document library.

image from book
Figure 7-3: An ECB menu item can be created for every document.

Code in the OnLoad event for DocumentManager3.aspx assumes that it can retrieve both the item ID for the document as well as the GUID for its document library from inside the QueryString of the incoming request. By using these pieces of information, the code inside DocumentManager3.aspx can create an SPList object for the document library as well as an SPListItem object for the document. It can also create an SPDocumentLibrary and SPFile object to display the information shown in Figure 7-4.

image from book
Figure 7-4: DocumentManager3.aspx uses the WSS object model to inspect document properties.

Keep in mind that it’s important in some scenarios to perform a test on an SPList object to ensure that it is compatible with the SPDocumentLibrary class before performing a conversion. However, in this particular scenario, this test is unnecessary because the ECB menu item used to redirect users to DocumentManger3.aspx is based on the Document content type. Therefore, this custom menu item appears only in the ECB menu of documents within document libraries. The following is a listing of the code in DocumentManager3.aspx that programs against the document from which the ECB menu item was selected:

  protected override void OnLoad(EventArgs e) {   // display information about current site   SPWeb site = SPContext.Current.Web;   lblSiteTile.Text = site.Title;   lblSiteUrl.Text = site.Url.ToLower();   // display document library information using SPList object   string ListId = Request.QueryString["ListId"];   lblListID.Text = ListId;   SPList list = site.Lists[new Guid(ListId)];   lblListTile.Text = list.Title;   lblRootFolderUrl.Text = list.RootFolder.Url;   // display document library information using SPDocumentLibrary object   SPDocumentLibrary documentLibrary = (SPDocumentLibrary)list;   lblDocumentTemplateUrl.Text = documentLibrary.DocumentTemplateUrl;   // display document information using SPListItem object   string ItemId = Request.QueryString["ItemId"];   SPListItem item = list.Items.GetItemById(Convert.ToInt32(ItemId));   lblDocumentName.Text = item.Name;   lblDocumentUrl.Text = item.Url;   // display document information using SPFile object   SPFile file = item.File;   lblFileSize.Text = file.TotalLength.ToString("0,") + " bits";   lblFileLastModified.Text = file.TimeLastModified.ToLocalTime().ToString();   lblFileCheckOutStatus.Text = file.CheckOutStatus.ToString(); } 

The previous example demonstrates a familiar pattern in a custom solution in which you extend an ECB menu with a menu item that allows the user to navigate to a custom application page that provides information or other options on a specific item or document. Though the custom application page shown here merely displays information, we leave it to your imagination as to how to extend this technique to create custom application pages that assist users by automating document management tasks.

Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
Year: 2007
Pages: 92 © 2008-2017.
If you may any questions please contact us: