While extremely unrecommended, sometimes you just want to fix a problem quickly and without jumping through a ton of hoops using the SharePoint SDK and writing a small C# program. I recently encountered this when I needed to delete a Folder from a whole bunch of Document Libraries across hundreds of sites. Making sure that I checked to see if the folder was there first, calling the right Web, calling the right List, getting the right Folder GUID, etc. all seemed like a lot of work for a simple SQL statement so I cheated.
Folders all stored in the AllUserData table with a tp_ContentType = 'Folder'. I was able to write a simple UPDATE statement like the following to delete the unwanted folder across the sites I wanted -
Quick and very dirty. Not recommended, kids don't try this at home.
Just a FYI to the 3 of you reading this site (Hi Mom and Dad!), I made an update to the Recreating the SharePoint Quick Launch story I posted back in February. I made some code fixes that were mainly introduced with the release of the 40 Free WSS Application Templates made by Microsoft.
Hope the bug fixes help!
When working with SharePoint v3 list data, it's important to understand the data structure used to store list items. List data is stored in the AllUserData table and the name should be your first indication that you might run into a problem if trying to pull or manipulate data stored in this table. Why? Well, "all" user data is stored in that table including deleted data and you will need to know how to filter out deleted items and that is where the tp_DeleteTransactionID column comes into focus.
The tp_DeleteTransactionID column is a varbinary field so you cannot readily read the contents of that column in SQL Server Enterprise Manager. This coulmn acts as a "deleted flag" and you will need to be able to know what items you have removed from your list, but you forgot to remove from your Recycle Bin. List items that you delete from your list, but that remain in your Recycle Bin will have the value of the tp_DeleteTransactionID column updated from the default value of 0x. Once you remove the items from your Recycle Bin, the item's corresponding rows will be deleted from the AllUserData, but until you do so those item's rows will remain in the AllUserData table.
So, if you want to filter out deleted content for the purpose of data manipulation or transformation, but you don't want to have to worry that the user has remembered to empty the Recycle Bin, add the following WHERE clause to your SQL statements -
Notice, that the value 0x is not surrounded by value defining apostrophes since the value is not a true string, numeric, date, etc. value, but a hex representation for the varbinary field.
Today, I encountered an error that I wasn't immediately sure how to fix while creating a new web part. After uploading the .DWP file to the web part gallery, I attempted to add the web part to a recently created web part page. Even though I had a Try/Catch/Finally wrapper around my code, I received a browser alert that had the following error:
After a very quick search for that error on Google's main search index and Google Groups, I found zero results. Then, I remembered that I didn't make the necessary corrections to the .DWP file - in particular, I left the Assembly field blank. After adding the correct assembly name, I saved the file, deleted the previously uploaded DWP file, re-uploaded the new DWP file and was able to successfuly add the web part. Success!
Anyone who has worked with SharePoint extensively knows that when troubleshooting SharePoint errors you will be confornted with some of the most meaningless, generic error messages of any major application around. However, thanks to articles like this (which is where I first read this tip), SharePoint developers and administrators can turn on more descriptive, "friendly" error messages by making two changes to your web applications web.config file -
Making these two changes in your development environment will really help your SharePoint developers and admins troubleshoot technical issues.
UPDATE: 2007 May 2nd - I've updated this article to include a lot of bug fixes that I have encountered along the way. I will maintain some type of versioning at the end of this post so readers can follow code changes that have occurred over time.
Microsoft's SharePoint Services 2007 (v3) provides so many additional features and bug fixes to their previous version that I was extremely anxious to upgrade our company's intranet to the newer code. However, as with any upgrade, you eventually run into stumbling blocks that will make your life miserable as a programmer/designer.
I recently came across one such issue with SharePoint Services 2007 - when you create a new page through the front-end, the Quick Launch menu is lost. Huh?
The Quick Launch menu is the standard left side navigation module that holds link information. In addition to the site specific links that you add to the Quick Launch, a static "View All Site Content" link is docked at the top of this list (which allows users to see a listing of all the Lists, Document Libraries, Picture Libraries, Surveys, Discussions and more. The items displayed in the quick launch are managed through the "Site Settings" option underneath the "Site Actions" button.
However, there is one major problem with the default SharePoint implementaion - when a user creates a new page (whether a basic or web part page), the Quick Launch menu does not attach itself to that new page. For some unknown reason, a major navigational module is dropped from secondary pages created through the SharePoint front-end. I would say that this decision is dubious at best. Since our company wanted to dock our Quick Launch onto every new page created on every site we maintain, we needed to programmatically come up with a solution. I decided to solve this problem by recreating the Quick Launch as a custom web part.
How do you reconstruct the Quick Launch from scratch? First, as with most advanced custom SharePoint tasks, you need to understand how the database is structured. Many SharePoint admins will tell you to "leave the SharePoint database alone", but simple reads to the database will not cause a problem (in fact, as long as you understand the database structure, (although not supported) I have created procedures that update the SharePoint database directly by pulling information from other data sources). The Quick Launch information is stored in two tables - Webs and NavNodes. The Webs table stores information concerning the different sites that have been created. It's important to first pull the 'id' for the for the site the user is currently browsing.
How do you tell what site a user is on? One such method would be to get the URL through Page.Request.Url.AbsoluteUri, then get the array(3) value and use that value for the 'fullurl' where clause(and if your dataset = 0 rows, you are on the default site, otherwise you are on a subsite). One problem with this method is that you cannot have a Document Library that shares the same name as a subsite (however, this is the option we implemented for now until I develop something better). Next, you take that 'id' value and query the NavNodes table for the record containing the root Quick Launch information for that site. You'll grab the 'eid' value for that record and then use that value to grab all the records where that eid value is found in the 'eidparent' column (for that root site and subsite).
This will give you all the "Link Headings" for that site's Quick Launch. Read these values into an array. First, render these headings using the ms-navheader class. Before moving onto the next "Link Heading", we must check to see if there are any "children" or "Link Items" for this heading. That would be done by running the following query:
If your dataset count is > 0, then read these "Link Items" into an array and render them all out while applying the ms-navitem class. Once all the "Link Items" are printed out, move to the next "Link Heading" until you have read and rendered all headings. So, there's the logic behind recreating the Quick Launch. Here's a snippet of some of the code I used to render my Quick Launch recreation (GUIDs changed to protect the innocent):
41 Try
42 strURL = Page.Request.Url.AbsoluteUri
43 arrURL = Split(strURL, "/", -1, CompareMethod.Text)
44
45 #If PROD = "Intranet" Then
46 strRootGUID = "{035AA977-25EF-4B20-AFF4-3BBF028CCE6C}"
47 strRootURL = "intranet.domain.com"
48 SQLConnection.ConnectionString = db.GetDBShare3Intranet
49 #ElseIf PROD = "Extranet" Then
50 strRootGUID = "{6B052A1E-F7FE-4A42-849F-A55079A49E3A}"
51 strRootURL = "portal.domain.com"
52 SQLConnection.ConnectionString = db.GetDBShare3Extranet
53 #ElseIf PROD = "Development" Then
54 strRootGUID = ""
55 strRootURL = "development:16542"
56 SQLConnection.ConnectionString = ""
57 #End If
58
59 SQLConnection.Open()
60 SQLQuery = "select id, fullurl from webs where siteid = '" & strRootGUID & "' and fullurl = '" & arrURL(3) & "'"
61 Dim DataAdapter As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter(SQLQuery, SQLConnection)
62 DataAdapter.Fill(DataSet)
63
64 If DataSet.Tables(0).Rows.Count > 0 Then
65 ' Match made - we are on a subsite
66 GUID = DataSet.Tables(0).Rows(i).Item(0)
67 strGUID = GUID.ToString
68 arrURL(3) = arrURL(3)
69 Else
70 ' No match made - we are on the Root Site
71
72 #If PROD = "Intranet" Then
73 strGUID = "{01872F73-68B8-44EC-AE6E-E0FCCF231AB5}"
74 #ElseIf PROD = "Extranet" Then
75 strGUID = "{E3DB7959-A1A7-4619-AC7D-F0B4E67D2DC6}"
76 #ElseIf PROD = "Development" Then
77 strGUID = ""
78 #End If
79
80 arrURL(3) = ""
81 End If
82
83 DataSet = New DataSet
84 SQLQuery = "select eid, numchildren, rankchild, url, name, siteid from navnodes where eidparent in (select eid from navnodes where webid = '" & strGUID & "' and siteid = '" & strRootGUID & "' and name = 'Quick Launch') and webid = '" & strGUID & "' and siteid = '" & strRootGUID & "' order by rankchild"
85 DataAdapter = New SqlClient.SqlDataAdapter(SQLQuery, SQLConnection)
86 DataAdapter.Fill(DataSet)
87
88 If DataSet.Tables(0).Rows.Count > 0 Then
89 output.RenderBeginTag("div class=ms-quicklaunchouter")
90 output.RenderBeginTag("div class=ms-quicklaunch style=" & Chr(&H22) & "width:100%; border: none;" & Chr(&H22))
91 output.RenderBeginTag("table cellpadding=0 cellspacing=0 class=ms-quicklaunch width=100%")
92 output.RenderBeginTag("tr")
93 output.RenderBeginTag("td class=ms-quicklaunchheader style=" & Chr(&H22) & "border: none;" & Chr(&H22))
94 output.Write("Navigation")
95 output.RenderEndTag()
96 output.RenderEndTag()
97
98 For i = 0 To DataSet.Tables(0).Rows.Count - 1
99 output.RenderBeginTag("tr")
100 output.RenderBeginTag("td class=ql-navheader")
101 ListGUID = DataSet.Tables(0).Rows(i).Item(5)
102 strListGUID = ListGUID.ToString
103
104 ' Start adding exception handling ...
105 ' 1) Links that start with http:// shouldn't append to the root.
106
107 If Left(DataSet.Tables(0).Rows(i).Item(3), 7) = "http://" Then
108 output.RenderBeginTag("a class=ms-navheader href=" & System.Web.HttpUtility.UrlPathEncode(DataSet.Tables(0).Rows(i).Item(3)))
109 Else
110 ' Write the "nomral" quick launch URL scheme
111 output.RenderBeginTag("a class=ms-navheader href=http://" & strRootURL & System.Web.HttpUtility.UrlPathEncode(DataSet.Tables(0).Rows(i).Item(3)))
112 End If
113 output.RenderBeginTag("b")
114 output.Write(DataSet.Tables(0).Rows(i).Item(4))
115 output.RenderEndTag()
116 output.RenderEndTag()
117 output.RenderEndTag()
118 output.RenderEndTag()
119
120 If DataSet.Tables(0).Rows(i).Item(1) > 0 Then
121 ' There are child items for this menu item
122
123 DataSet2 = New DataSet
124 SQLQuery = "select eid, numchildren, rankchild, url, name, DocID from navnodes where eidparent in (select eid from navnodes where eid = '" & DataSet.Tables(0).Rows(i).Item(0) & "' and webid = '" & strGUID & "' and siteid = '{035AA977-25EF-4B20-AFF4-3BBF028CCE6C}') and webid = '" & strGUID & "' and siteid = '{035AA977-25EF-4B20-AFF4-3BBF028CCE6C}' order by rankchild"
125 DataAdapter = New SqlClient.SqlDataAdapter(SQLQuery, SQLConnection)
126 DataAdapter.Fill(DataSet2)
127
128 For x = 0 To DataSet2.Tables(0).Rows.Count - 1
129 output.RenderBeginTag("tr")
130 output.RenderBeginTag("td")
131 output.RenderBeginTag("table class=ms-navitem width=100% border=0 cellpadding=0 cellspacing=0")
132 output.RenderBeginTag("tr")
133 output.RenderBeginTag("td width=100% style=" & Chr(&H22) & "border: none;" & Chr(&H22))
134
135 If IsDBNull(DataSet2.Tables(0).Rows(x).Item(3)) = False Then
136 ' URL field in NavNodes has a value
137 output.RenderBeginTag("a class=ms-navitem href=http://" & strRootURL & System.Web.HttpUtility.UrlPathEncode(DataSet2.Tables(0).Rows(x).Item(3)))
138 Else
139 ' URL field is Null, now, have to use DocID value to obtain path
140 DataSet3 = New DataSet
141 DocGUID = DataSet2.Tables(0).Rows(x).Item(5)
142 strDocGUID = DocGUID.ToString
143 SQLQuery = "select DirName, LeafName from AllDocs where id = '" & strDocGUID & "'"
144 DataAdapter = New SqlClient.SqlDataAdapter(SQLQuery, SQLConnection)
145 DataAdapter.Fill(DataSet3)
146 output.RenderBeginTag("a class=ms-navitem href=http://" & strRootURL & "/" & System.Web.HttpUtility.UrlPathEncode(DataSet3.Tables(0).Rows(0).Item(0)) & "/" & System.Web.HttpUtility.UrlPathEncode(DataSet3.Tables(0).Rows(0).Item(1)))
147 End If
148
149 output.Write(DataSet2.Tables(0).Rows(x).Item(4))
150 output.RenderEndTag()
151 output.RenderEndTag()
152 output.RenderEndTag()
153 output.RenderEndTag()
154 output.RenderEndTag()
155 output.RenderEndTag()
156 Next
157 End If
158 Next
159
160 output.RenderEndTag()
161 output.RenderEndTag()
162 output.RenderEndTag()
163 End If
164
165 DataSet = Nothing
166 SQLConnection.Close()
167 Catch ex As Exception
168 strError = ex.Message.ToString
169 output.Write(strError)
170 Finally
171
172 End Try
If you see any improvements that I can make, let me know by leaving me a comment. Hope this helps someone.
Versioning
Recent Comments
2 weeks 1 day ago
2 weeks 2 days ago
4 weeks 3 days ago
6 weeks 3 days ago
1 year 12 weeks ago
1 year 12 weeks ago
1 year 13 weeks ago
1 year 14 weeks ago
1 year 17 weeks ago
1 year 17 weeks ago