Creating a link-enabled document library using Windows SharePoint Services and .NET 2.0Joris Poelmans
Applies to:Dolmen
An interesting customisation for Windows SharePoint Services (WSS) is the ability to track relationships between documents. In this article we will create an extension for WSS which will allow users to link documents to each other. This extension will be written in .NET 2.0 code to demonstrate how you can use Visual Studio 2005 to enhance your SharePoint sites. To be able to create a document linking solution on WSS with ASP.NET 2.0, we will need to complete a number of steps: 1. Upgrading Windows SharePoint Services to ASP.NET 2.0 2. Creating a custom SharePoint list to store the relationships between the documents 3. Extend the SharePoint document library context menu 4. Add your custom administration page for linking documents Contents: Step 1: Upgrading Windows SharePoint Services to ASP.NET 2.0With the release of Windows SharePoint Services Service Pack 2 (WSS SP2), it is possible to use .NET 2.0 code in your SharePoint applications. After installing WSS SP2 you should upgrade your WSS sites to be able to use the .NET 2.0 framework. Run stsadm.exe from command line:
stsadm.exe –o upgrade –forceupgrade –url http://URLOftheVirtualServer}This operation will make some changes to the web.config to facilitate for changes in the ASP.NET 2.0 security model and to disable ASP.NET 2.0 event validation. The –forceupgrade option will also upgrade the schema of the attached content databases. After upgrading your WSS sites you will be able to change the version of ASP.NET of SharePoint by following these steps: 1. Click Start, click Run, type inetmgr and then click OK 2. In the Internet Information Services (IIS) Manager console, expand the nodes until the SharePoint virtual server is visible under the Web Sites node. 3. Right-click the Web application, and then click Properties. 4. On the Properties page, click the ASP.NET tab, and then change the version of ASP.NET to 2.0 ![]() Attention: Although you can use .NET 2.0 for WSS after installing Service Pack 2, SharePoint Portal Server (SPS) must still be configured to use .NET 1.1. If you want to run both SPS and WSS with .NET 2.0 on the same server, you should create a separate virtual server for WSS and extend it. To do this, complete the following steps:
![]() Step 2: Creating a custom SharePoint list to store relationships between documentsWhen you need to make changes to SharePoint site definitions, it is considered best practice to copy an existing default SharePoint site definition. (For more information see Supported and unsupported scenarios for working with custom site definitions) We are going to create a copy of the default SharePoint teamsite site definition and alter it by adding in a custom list called DocLinks.
1. Make a copy of the WEBTEMP.XML file located at Local_Drive:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\TEMPLATE\1033\XML.. Rename the file to WEBTEMPSTSDOCLINKS.XML 2. Open WEBTEMPSTSDOCLINKS.XML and delete all the code between the TEMPLATES tags 3. Copy the code listed below between the TEMPLATES tags <Template Name="STSDOCLINKS" ID="10002">
<Configuration ID="0" Title="Project Site with document linking" TYPE="0"
Hidden="FALSE"
ImageUrl="/_layouts/images/stsprev.png"
Description="This template provides a project site with document linking.">
</Configuration>
</Template>4. The Name attribute must contain the same name, in all capital letters, that is assigned to the new folder. Also, in order to avoid conflict with IDs already used in Windows SharePoint Services, use unique values greater than 10,000 for the ID attribute.
5. Save the XML file 6. Reset Internet Information Services (IIS) for the new template to appear as an option on the Template Selection page. Go to Start>Run. Type iisreset. 7. Create a new site based on the newly created site definition A site definition contains a list definition for each type of list (for example, announcements, tasks, document libraries, and so on) so the easiest way to create a custom list definition is to start from an existing list definition. The files used in a list definition are found in one of the subfolders of Drive:\Program Files\Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\Locale_ID\Site_Definition\LISTS. Each folder listed in here contains a set of files:
1. Navigate to the following directory on your SharePoint server in the custom site definition folder you just created: Drive:\Program Files\Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\1033\STSDOCLINKS\LISTS 2. Make a copy of the CUSTLIST list definition and paste it into the same directory 3. Rename the folder to DOCLINKSLIST Within the list definition folder, you will find the ASP.NET and XML files which provide the functionality necessary to view and edit the list. In this same directory you will also find a XML file named SCHEMA.XML. This file contains the full definition for your list. 1. In the DOCLINKSLIST list definition directory, open the SCHEMA.XML file. 2. In SCHEMA.XML you will see the top-level LIST element. This element defines the schema and views for the new list. 3. Replace the List start tag with the listed code below: <List xmlns:ows="Microsoft SharePoint" Name="doclinks" Title="Document Link List" Direction="0" Url="Lists/DOCLINKSLIST" BaseType="0" >The List element contains two child elements. The MetaData element defines the fields in the list and the Data element is used if you want a list to contain data when it is first created. The MetaData element contains the child elements DefaultDescription, Fields, Views, Toolbar and Forms. The DefaultDescription is used on the Documents and Lists page when a list is created. This field is mandatory if you want to automatically create the list at site creation. If you fail to do this, you will get the generic “Action can not be completed” error at site creation. <MetaData> <DefaultDescription>Document link repository</DefaultDescription>Next you need to define the list columns in the Fields element. The Fields element contains a set of Field elements that define the schema for the different list columns. If you want to add an additional field to a list, you can do this by inserting a new Field element as child of the Fields element. For our solution, we are going to add 2 extra columns. The following code should be added to: <Fields> <Field Name="MainDocURL" Type="Text" DisplayName="Main document URL" Required="TRUE" Sealed="TRUE"/> <Field Name="LinkedDocURL" Type="Text" DisplayName="Linked document URL" Required="TRUE" Sealed="TRUE" />In order to display the list as an option on the Create page in SharePoint, you need to specify the new list within the ListTemplates element of the ONET.XML file of your custom site definition. The ListTemplates element contains a set of ListTemplate elements. Each ListTemplate element is a reference to a list definition in the Lists directory of the custom site definition. 1. Add a new LISTTEMPLATE element. The ListTemplate element contains a number of attributes:
<ListTemplate Name="doclinkslist" DisplayName="Document Link List" Type="777" BaseType="0" OnQuickLaunch="TRUE" SecurityBits="11" Description="Custom list containing links between documents" Image="/_layouts/images/itgen.gif" Hidden=”False”> </ListTemplate>3. Save ONET.XML 4. Perform an iisreset To actually create the list whenever a new site is created based on the custom site definition, you will need to add some extra changes to ONET.XML. Which lists, web parts and web part pages are used in a site is defined in a Configuration within ONET.XML. Add your custom list to the <Configuration>. The <Lists> element contains the collection of lists for a specific configuration. The Configuration ID refers to our WEBTEMPSTSDOCLINKS.XML file. <Configuration ID="0" Name="Default"> <Lists> <List Title="Document links" Type="999" /> … </Lists> </Configuration> ![]() Step 3: Extend the SharePoint document library context menuFor each item in a Windows SharePoint Services (WSS) document library a context sensitive drop-down menu is shown. For this application we are going to add an option to enable document linking.
![]() The context menu for list items is generated by client-side Javascript contained in a file OWS.JS, which you can find in the folder \Program Files\Common Files\Microsoft Shared\web server extensions\ 60\TEMPLATE\LAYOUTS\1033. It is however possible to override functions in this file and to modify it according to your needs. But because OWS.JS is used by all sites in your SharePoint environment, it is not a good idea to modify OWS.JS directly. Instead you should make sure that changes only apply to your own custom site definition. To accomplish this, SharePoint provides a mechanism to override the functions in the OWS.JS file through the use of the CustomJSUrl attribute in ONET.XML. The Project element in the ONET.XML file has an attribute CustomJSUrl in which you can provide your own javascript file that will be inserted into the pages of your template after the default OWS.JS. 1. Navigate to the following directory on your SharePoint server: \Program Files\Common Files\Microsoft Shared\web server extensions\ 60\TEMPLATE\LAYOUTS\1033 2. Make a copy of the ows.js javascript file and paste it into the same directory 3. Rename the copy to ows_custom.js 4. Navigate to the following directory on your SharePoint server in the custom site definition folder you just created: Drive:\Program Files\Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\1033\STSDOCLINKS\XML and open ONET.XML for editing 5. Modify the Project element <Project Title="Team Web Site" ListDir="Lists" xmlns:ows="Microsoft SharePoint" CustomJSUrl="/_layouts/[%=System.Threading.Thread.CurrentThread. CurrentUICulture.LCID%]/ows_custom.js">6. Perform an iisreset The actual menu is generated by the the AddDocLibMenuItems function. This function is already implemented in such a way that you can plug in your own code. function AddDocLibMenuItems(m, ctx) {
if (typeof(Custom_AddDocLibMenuItems) != “undefined”) {
if (Custom_AddDocLibMenuItems(m, ctx)) {
return;
}
}
}By implementing Custom_AddDocLibMenuItems in a custom javascript file you can easily add your own functionality to the context menu. Open OWS_custom.js and insert the code below to add an two extra menuitem to the contextmenu.
function Custom_AddDocLibMenuItems(m, ctx)
{
var strDisplayText = "";
var strAction;
var strImagePath = "";
// parse the URL out of the itemTable
var URL = "";
var index = itemTable.innerHTML.indexOf("href=");
if (index > 0)
{
var str = itemTable.innerHTML.substr(index + 6);
index = str.indexOf('"');
if (index > 0)
{
URL = str.substr(0, index);
}
}
if (URL != "")
{
var currentItemEscapedFileUrl =
escapeProperly(unescapeProperly(URL));
linkToUrl = ctx.HttpRoot +
"/_layouts/DOLMEN/linkto.aspx?docUrl=" + currentItemEscapedFileUrl;
strDisplayText = "Add links";
strAction = "OpenLinkToPage('" + linkToUrl + "')";
strImagePath = ctx.imagesPath + "itportlst.gif";
CAMOpt(m, strDisplayText, strAction, strImagePath);
linkToUrl = ctx.HttpRoot +
"/_layouts/DOLMEN/managelinks.aspx?docUrl=" + currentItemEscapedFileUrl;
strDisplayText= "View/Remove links";
strAction = "OpenLinkToPage('" + linkToUrl + "')";
strImagePath = ctx.imagesPath + "itdlsm.gif";
CAMOpt(m, strDisplayText, strAction, strImagePath);
CAMSep(m);
}
return false;
}
function OpenLinkToPage(linkToUrl)
{
var linkToPage = window.open(linkToUrl, "linkToUrl",
"width=700,height=430,nomenubar,noscrollbars,notoolbar");
linkToPage.focus();
}
The CAMSep and CAMOpt – shown in the codelisting above - are both SharePoint specific functions which are defined in menu.js. The CAMOpt function is called to create individual menu items. The CAMOpt function takes four parameters: the menu object to add the new item to, the display text of the menu item, the javascript action to perform when the item is clicked and a path to an image file to associate with the item. The image for the item is stored in the directory Drive:\Program Files\Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\IMAGES and is accessible through the imagesPath property of the context object. A call to the CAMSep function adds the separator bar to the menu. The “return false;” end statement is needed to make sure the standard menu items are also added to the menu; returning true indicates that these items should not be added.
![]() ![]() Step 4: Add your custom linking pageIn the previous step we modified the contextmenu to include a link to our own ASP.NET pages. These pages will be deployed into the _layouts virtual directory. All of SharePoints application pages are also deployed in the the /_layout/ virtual directory, e.g. the SharePoint pages to manage users, create lists, etc…
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
public partial class _Default: System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
SPWeb myweb = SPControl.GetContextWeb(this.Context);
this.lblTitle.Text = myweb.Title;
}
}
11. Open the page in your webbrowser – it should display the title of the underlying SharePoint site in your label.
Tip: The easiest way to start debugging your code from within the context of SharePoint is just adding System.Diagnostics.Debugger.Break() within your code. This will signal a breakpoint to an attached debugger or ask which debugger to attach. Both the linkto.aspx and managelinks.aspx page will have a similar look & feel. To keep things simple we are only going to allow linking between documents in the same document library. It is however not difficult to enhanced the described functionality and to include across document libraries and even across sites. On the the linkto.aspx webform we are going to display a GridView with all documents in the same documentlibrary to which we can link. The GridView is an enhanced version of the basic DataGrid, in combination with new data source controls, you can bind to data without writing any code. In our solution, we don’t want a document to be linked to itself, so we need to further filter out the items in the list. This can be accomplished using a SPQuery object. After instantiating an SPQuery object, you can use Collaborative Application Markup Language (CAML) to define criteria for the query, which is passed as parameter in the GetItems method. (For more info: see http://msdn.microsoft.com/library/en-us/spptsdk/html/tscamlovIntroduction_SV01029856.asp ) The GetItems method of the SPList class returns a collection of items from the list based on the specified query. private SPListItemCollection _splistitems;
private SPWeb _spweb;
private string sSourceUrl;
private string sSourceName;
private string sFolderUrl;
private SPList _splist;
private Guid guidDocLib;
protected void Page_Load(object sender, EventArgs e)
{
_spweb = SPControl.GetContextWeb(this.Context);
sSourceUrl = Request.QueryString["docUrl"].ToString();
sSourceUrl = SourceUrl.Substring(sSourceUrl.IndexOf(_spweb.ServerRelativeUrl));
SPFile _spfile = _spweb.GetFile(sSourceUrl);
sSourceName = _spfile.Name;
sFolderUrl = _spweb.Name + "/" + _spfile.ParentFolder;
guidDocLib = _spfile.ParentFolder.ContainingDocumentLibrary;
if (!Page.IsPostBack)
{
DataTable dtDocLinks = this.GetDocumentData();
this.GridView1.DataSource = dtDocLinks;
this.GridView1.DataBind();
}
}
protected DataTable GetDocumentData()
{
DataTable dtDocLinks=null;
_splist = _spweb.Lists[guidDocLib];
SPQuery _spquery = new SPQuery();
_spquery.Query = "<Where><Neq><FieldRef Name='FileLeafRef' />" +
"<Value Type='Text'>" + sSourceName + "</Value></Neq></Where>";
_splistitems = _splist.GetItems(_spquery);
dtDocLinks = _splistitems.GetDataTable();
return dtDocLinks;
}
In the gridview on the linkto.aspx webform, we are going to display 4 columns:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="linkto.aspx.cs"
Inherits="linkto" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Document link management</title>
<Link REL="stylesheet" Type="text/css" HREF="/_layouts/1033/styles/ows.css">
<link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/Menu.css">
<script src="/_layouts/1033/owsbrows.js"></script>
<script src="/_layouts/1033/ows.js"></script>
<script src="/_layouts/1033/menu.js"></script>
<script src="/_layouts/1033/ie55up.js"></script>
</head>
<body>
<form id="form1" runat="server">
<table cellpadding="2" cellspacing="3" border="0">
<tr><td> </td></tr>
<tr><td><table class="ms-toolbar" width="100%"><tr><td style="height:16px">
<asp:Label ID="lblTitle" runat="server">
Document link management - add links
</asp:Label>
</td></tr></table></td></tr>
<tr><td>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
CellPadding="3">
<Columns>
<asp:TemplateField><ItemTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# GetIconURL(DataBinder.Eval (
Container.DataItem, "FileLeafRef").ToString()) %>' />
</ItemTemplate></asp:TemplateField>
<asp:TemplateField HeaderText="Title"><ItemTemplate>
<a href="<%# GetFullUrl(DataBinder.Eval(
Container.DataItem, "FileLeafRef").ToString()) %>"
target="_blank">
<%# DataBinder.Eval( Container.DataItem, "Title") %></a>
<asp:Label ID="lblLinkedDocUrl" Visible="false" runat="server"
Text='<%# DataBinder.Eval( Container.DataItem, "FileLeafRef") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Modified" ItemStyle-Wrap=false>
<ItemTemplate>
<%# DataBinder.Eval( Container.DataItem,"Last_x0020_Modified") %>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Modified By">
<ItemTemplate>
<%# GetPresenceString(
DataBinder.Eval( Container.DataItem,"Editor").ToString())%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Select">
<ItemTemplate>
<asp:CheckBox ID="chkSelect" runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<HeaderStyle CssClass="ms-vh2" />
<RowStyle CssClass="ms-vb2" />
<EmptyDataRowStyle />
</asp:GridView>
</td></tr>
<tr>
<td align=right>
<div style="padding-right:5px"><asp:Button ID="butOK" runat="server"
Text="OK" OnClick="butOK_Click" /></div>
</td>
</tr>
</table>
</form>
</body>
</html>
![]() The presence icon as shown in SharePoint pages is generated by a javascript function IMNRC, which in turn calls an ActiveX control (called "name.dll") on the users machine. To use this presence icon in your custom pages only requires a reference to the necessary javascript files and some client-side javascript. protected string GetPresenceString(string sLoginName)
{
string s="";
SPUser _spuser = _spweb.AllUsers[sLoginName];
s = "<span><img border=\"0\" height=\"12\" width=\"12\"
src=\"/_layouts/images/blank.gif\" onload=\"IMNRC('" + _spuser.Email +
"')\" id=\"IMID1\" ShowOfflinePawn=1>" + _spuser.Name + "</span>";
return s;
}
Adding links to the Document Link list from within our custom page is pretty straightforward. An important thing to remember however is that you should explicitly call the Update method after making changes to a SPListItem. This call will update the database with the changes you made. Below is the code listing for updating the Document Link list with the checked items in the gridview.
protected void butOK_Click(object sender, EventArgs e) {
SPList _splistdoclinks = _spweb.Lists["Document Links"];
for (int i = 0; i < GridView1.Rows.Count; i++) {
GridViewRow row = GridView1.Rows[i];
bool isChecked = ((CheckBox)row.FindControl("chkSelect")).Checked;
if (isChecked) {
string s = ((Label)row.FindControl("lblLinkedDocUrl")).Text;
s = "/" + sFolderUrl + "/" + s;
//Add listitems
_spweb.AllowUnsafeUpdates = true;
SPListItem _splistitem = _splistdoclinks.Items.Add();
_splistitem["MainDocURL"] = sSourceUrl;
_splistitem["LinkedDocURL"] = s;
SPFile _spfile = _spweb.GetFile(s);
_splistitem["Title"] = _spfile.Title;
_splistitem.Update();
_spweb.AllowUnsafeUpdates = false;
}
}
string popupScript="<script language='javascript'>window.close();</script>";
ClientScript.RegisterStartupScript(typeof(Page),"PopupScript", popupScript);
}
Because I didn’t add a <SharePoint:FormDigest> control to my custom page, I need to set the AllowUnsafeUpdates property of the SPWeb class to true, to allow updates to the database.
In the second page “managelinks.aspx” we need to provide for a way to delete documentlinks. To avoid accidental deletions, the user will be shown a confirm messagebox upon clicking the delete link. This is easy to do because the ASP.NET 2.0 LinkButton has a an OnClientClick property in which you can specify client-side JavaScript that should be executed when the Button is clicked. <asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="lnkDelete" runat="server"
OnClientClick="return confirm('Are you sure you want to delete this link?')"
CommandName="Delete" Text="Delete"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
A GridView can include OnRowDeleting and OnRowDeleted event handlers to trap and program RowDeleting and RowDeleted events. In the RowDeleting event handler we actually delete the document link. RowDeleting is a new event in .NET 2.0, it is raised when a row's Delete button is clicked, but before the GridView control deletes the row. This also enables you to still cancel the event.
protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e) {
_splist = _spweb.Lists["Document Links"];
GridViewRow row = GridView1.Rows[e.RowIndex];
string s = ((Label)row.FindControl("lblLinkedDocUrl")).Text;
SPListItemCollection _splistitems = _splist.Items;
for (int j = 0; j < _splistitems.Count; j++) {
if (
(_splistitems[j]["MainDocURL"].ToString() == sSourceUrl) &&
(_splistitems[j]["LinkedDocURL"].ToString() == s))
{
_spweb.AllowUnsafeUpdates = true;
_splistitems.Delete(j);
_spweb.AllowUnsafeUpdates = false;
}
}
this.BindData();
}
![]() ![]() ConclusionWith the release of Windows SharePoint Services SP2 it is finally possible to do SharePoint development in Visual Studio 2005, there are however some things you should take care off. In this article I have shown a sample solution which uses different SharePoint components such as custom site and list definitions, javascript files and ASP.NET 2.0 webforms in the _layouts directory. This article also demonstrates that SharePoint can be quite easily used as a development platform in which you can leverage the built-in functionality.
![]() References
![]() About the author
| ||||||