Creating a “TagFilter” Web Part that interacts with pages in SharePoint 2007

Abstract

Becoming familiar with SharePoint 2007 web parts, CAML and implementing a custom web part that interacts with a SharePoint page’s CAML. This article also covers setting up a development environment, building, deploying, installing, and debugging a web part and includes a project linked at the bottom.

Intro

It is our teams’ goal this year to create an internal site for our Technical Evangelists to go to as a first stop resource for demos. We wanted to organize these demos so that they are easy to find, and we wanted to have the site in a blog-like format. It’s a demos’ nature to become stale with time, and so we thought that a blog format was a good fit for documenting demos. We really like the “tag clouds” concept since it surface’s keywords and their frequency of use and popularity.

We took a look at a few blogging solutions. The first was an ASP.NET sample “Blog Starter Kit.” While it’s a great example of how to code a blog server from the ground up, it proved to be too much work to get where we wanted in a short time.

I next took a look at Community Server 2.0. This is very robust and easy to use blogging software and contains a wealth of other community capabilities, but it ended up being overkill for our simple blogging needs. It also didn’t have the ability to easily customize the UI layout via its web interface; I wanted to be able to add announcements and contact information without having to manage any HTML, plus we needed to be able to add custom properties to the blog entries, such as demo storage location, which Community Server was not designed to enable without code, this meant becoming familiar with their architecture and writing custom code.

Having had some familiarity with SharePoint, and knowing its great support for being able to customize lists, I then examined SharePoint 2007’s blog capabilities, and while they are certainly not as developed as Community Servers’, they were a good starting point for our proposed solution. Out-of-the-box SharePoint 2007 has the capability of creating blog sites with ease. It creates a home page that contains a Blog Post List web part (based on ListViewWebPart.) These blogs can have multiple categories assigned to them with items from the “Category” List, and they can also have multiple comments attached with items from the “Comments” List. The homepage contains a list of all posts in a summary view layout. A second, but equally important, reason for having chosen SharePoint is that I wanted to have a better understanding of its inner workings.

From the Category List, the user can drill down into the category page which out-of-box only displays items from a single category. I found this to be too constraining for our needs, and so I set out to modify Category.aspx to allow filtering with multiple categories, plus create a way to navigate the page in a more efficient and meaningful way with a “Tag Filter” control that would look something like this:

I started out with the “Tag Cloud” web part that I found on www.codeplex.com as a starting point which was very useful, but I needed to change it dramatically to meet our requirements since they were quite different. See resources for more info on the “Tag Cloud” web parts

Definitions

Not the most exciting, but necessary never-the-less to understand later portions of this writing.

CAML – Collaborative Application Markup Language – not to be confused with the Caml programming language, is an XML based language that contains tags to define and display data. In this post we will focus on CAML’s query language and query language schema.

SharePoint Designer 2007 – A tool to edit SharePoint pages along with CAML embedded in those pages. Since the web part CAML markup is stored in SQL, opening files via UNC (e.g. \\<my share point server name>\blogs\default.aspx) from a SharePoint Server within normal editing tools, will not show the CAML markup, so it’s necessary to edit pages with SharePoint Designer 2007 to surface and edit the CAML. Incidentally here’s the trial version of SharePoint Web Designer 2007.

SharePoint Web Part – This is the fundamental unit that can access SharePoint information and display itself on a SharePoint page. With SharePoint 2007, the Office team has made it much easier to develop web parts, since they are now leveraging ASP.NET’s web part infrastructure. See resources section for required reading on web parts that will help you along with development.

Getting Started

The Blog Home page

The default.aspx SharePoint blog home page shows all blog posts that have been created. The two web parts that we will find most interesting on this page are the Category web part and the Posts web part. The Category part enumerates all categories that have been defined. For example: a category of “MyTag” would link to “/<blogsitename>/lists/categories/category.aspx?Name=MyTag”. Note that “Category” and “Tag” will sometimes be used interchangeably in this writing. The Posts part shows all Blog posts ordered by PublishedDate and ID. How do I know how the Posts are ordered? Well, I could edit the page in the browser, go into the View Settings, and look at the settings for its current view, but that won’t help us later when we are trying to dissect the category.aspx page. So let’s instead open the default.aspx page in the SharePoint Designer and take a look at the web part titled “Posts”.

CAML

The web part XML that you will find contains all the default and user-defined settings for the Posts part. A large portion of these settings are exposed through the web management UI, but the ListViewXml property is not. Instead the Web based View Settings UI manages most of the capabilities of this property.

Notice that embedded in the CAML (escaped to avoid collisions in SharePoint Designer) inside the ListViewXml property is a query node. That looks like this:

  &lt;Query&gt;

    &lt;OrderBy&gt;

      &lt;FieldRef Name="PublishedDate" Ascending="FALSE"/&gt;

      &lt;FieldRef Name="ID" Ascending="FALSE"/&gt;

    &lt;/OrderBy&gt;

  &lt;/Query&gt;

Converted to XML, it appears much more readable as:

  <Query>

    <OrderBy>

      <FieldRef Name="PublishedDate" Ascending="FALSE"/>

      <FieldRef Name="ID" Ascending="FALSE"/>

    </OrderBy>

  </Query>

The Category.aspx Page

So now that we know where the CAML and its Queries are serialized, we can move on to taking a look at the Category.aspx page. The Category.aspx page shows only Post items that have been tagged with a name that is passed as a parameter to the page. How does the Category.aspx page capture a request parameter? The answer lies in the CAML of the Posts web part which is also included on the category.aspx page. Inspecting the Category.aspx page with the SharePoint Designer this is how the Posts part markup appears inline. Taking a look at ListViewXml reveals this CAML XML. We find the Query node and see now that it is more complex. Looking at just the Query node again, we now see a “Where” clause that compares the “PostCategory” value with the “Name” request variable:

  <Query>

    <OrderBy>

      <FieldRef Name="PublishedDate" Ascending="FALSE"/>

      <FieldRef Name="ID" Ascending="FALSE"/>

    </OrderBy>

    <Where>

        <Eq>

          <FieldRef Name="PostCategory"/>

          <Value Type="">

            <GetVar Scope="Request" Name="Name"/>

          </Value>

        </Eq>

    </Where>

  </Query>

If we make some minor changes to this query, we can get some much more interesting results, for example: if I wanted to find all Posts that contain the word “Demos” and “WPF”. I could pass the following to the Category.aspx page: “Category.aspx?N1=Demos&N2=WPF” and have the following CAML embedded process the request:

  <Query>

    <OrderBy>

    <FieldRef Name="PublishedDate" Ascending="FALSE"/>

      <FieldRef Name="ID" Ascending="FALSE"/>

    </OrderBy>

    <Where>

      <And>

  <Eq>

          <FieldRef Name="PostCategory"/>

            <Value Type="">

              <GetVar Scope="Request" Name="N1"/>

            </Value>

        </Eq>

        <Eq>

          <FieldRef Name="PostCategory"/>

            <Value Type="">

              <GetVar Scope="Request" Name="N2"/>

            </Value>

        </Eq>

      </And>

    </Where>

  </Query>

And then taken to the extreme; we’d like to retain the original “Name” parameter, for backwards compatibility, so that pre-existing links don’t break, and we’d like to have the ability to filter up to 6 Categories deep. (Note that if values are omitted for any of the parameters, no results will be returned. The drawback to this approach, due to the AND query logic, is that we need to always pass a parameter for all request variables; otherwise the query returns no results. So, for this sample to work we need to create a common category “All” and (important for this to work) tag every single Blog Post with it. So our final query will be:

  <Query>

    <OrderBy>

      <FieldRef Name="PublishedDate" Ascending="FALSE"/>

      <FieldRef Name="ID" Ascending="FALSE"/>

    </OrderBy>

    <Where>

      <Or>

        <And>

          <And>

            <And>

              <And>

                <And>

                  <Eq>

                    <FieldRef Name="PostCategory"/>

                    <Value Type="">

                      <GetVar Scope="Request" Name="N1"/>

                    </Value>

                  </Eq>

                  <Eq>

                    <FieldRef Name="PostCategory"/>

                    <Value Type="">

                      <GetVar Scope="Request" Name="N2"/>

                    </Value>

                  </Eq>

                </And>

                <Eq>

                  <FieldRef Name="PostCategory"/>

                  <Value Type="">

                    <GetVar Scope="Request" Name="N3"/>

                  </Value>

                </Eq>

              </And>

              <Eq>

                <FieldRef Name="PostCategory"/>

                <Value Type="">

                  <GetVar Scope="Request" Name="N4"/>

                </Value>

              </Eq>

            </And>

            <Eq>

              <FieldRef Name="PostCategory"/>

              <Value Type="">

                <GetVar Scope="Request" Name="N5"/>

              </Value>

            </Eq>

          </And>

          <Eq>

            <FieldRef Name="PostCategory"/>

            <Value Type="">

              <GetVar Scope="Request" Name="N6"/>

            </Value>

          </Eq>

        </And>

        <Eq>

          <FieldRef Name="PostCategory"/>

          <Value Type="">

            <GetVar Scope="Request" Name="Name"/>

          </Value>

        </Eq>

      </Or>

    </Where>

  </Query>

So now a request for all Posts that contain “Demos” and “WPF” would be: “Category.aspx?N1=Demos&N2=WPF&N3=All&N4=All&N5=All&N6=All”

Setting up a dev environment

First things first, we need to set up the development environment for building/debugging a SharePoint web part. Optimally, it‘s best to set up the Visual Studio 2005 development environment on the SharePoint Server itself, so that you can debug the SharePoint web part. The resources section below contains links that will walk you through some of the basics of creating a web part, though I’d like to provide some basic and additional comments.

You will need to copy the Microsoft.Sharepoint.dll (9Mb) from the SharePoint server into your web part project root to get it to properly compile. I found mine in “C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI” Note that some of the documentation points you to other directories for earlier versions of SharePoint. There was a naming convention change, and so for SharePoint 2007 the correct files are located in the \12\ISAPI directory standing for Office 2007 (or Office “12”). The \60\ISAPI directory is for Office 2003 (or version 6.0).

Registering and installing a web part

Manually registering and installing a web part is not a trivial task, but I’ve taken the following steps to make it easier to version the web part.

1. Make sure that your web part is “strong name signed”. If you use the project that I’ve provided, the build process will take care of this (essentially you just need to add a key file to your solution, and in project property settings choose it to sign the assembly.) If you happen to create a new key, you will have to determine its new PublicKeyToken and adjust this value anywhere it occurs (safecontrol entry and dwp file). See article 1 in the resources section below for direction.

2. In a default installation of SharePoint server the files for the site itself will be contained in the following directory: “C:\Inetpub\wwwroot\wss\VirtualDirectories\80.” Yours may vary, but the 80 stands for port 80. In your web part project you will want to make sure that the assembly that is created, when building the project, gets copied to the BIN directory in your SharePoint Site. This means adding something like the following to the PostBuildEvent property in your project:

xcopy /y $(TargetPath) "C:\Inetpub\wwwroot\wss\VirtualDirectories\80\bin\"

3. Add the following line to the “C:\Inetpub\wwwroot\wss\VirtualDirectories\80\web.config” file in the <SafeControls> section: <SafeControl Assembly="UberdemoWebparts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d4dfd62c364ed482" Namespace="UberdemoWebparts" TypeName="*" Safe="True"/>

4. Make the following change in the trust level in the same web.config file from WSS_Minimal to WSS_Medium in the trust node so that it appears so: <trust level="WSS_Medium" originUrl="" /> (Note that this is a very insecure way to install web parts. Anyone can install them if they have permissions on the machine, but it is also the least pain free way for doing development. See article 3 in the resources section below, how to properly deploy a web part in a production environment)

5. You must create a DWP file for each web part in an assembly, yet one assembly can contain multiple web parts, and be declared once as safe in the Web.config file. See Resource article 1 below on how to properly format a DWP file. (note that the project provided has a TagFilter.dwp file in the root solution folder.)

6. You should now build the web part in Visual Studio 2005, and ensure that the DLL has been copied into the site’s BIN directory.

7. Now you need to add a reference the web part in a page. Visit the page that you would like to install the web part on and click “Site Actions->Edit Page”

8. Click on “Add a Web Part” on the page, and in the dialog that appears click the “Advanced Web Part Gallery and options” link.

9. Click on the “Browse” sub-heading just below the “Add Web Parts” heading, and click the “Import” menu item in the menu that appears.

10. Click the browse button and browse to the DWP file for the web part that you would like to install and click upload.

11. This step will determine whether you have the web part installed properly: drag the web part from the ToolBox menu onto the page anywhere. If you get an error at this time it will most likely be something to the effect of “your web part is not registered as safe”. Check steps 1-6 to ensure that all are correct and then repeat steps 7-11.

12. You should now see the added web part displaying on your page.

Debugging a web part

Debugging can be tricky at first, but once you get it set up it becomes much easier. In some of the below articles referenced in the Resources section it states that you need to attach to the w3wp.exe process, but it doesn’t say that there might be 3 running, and which one to choose from. I solved this by going into the IIS Manager and stopping the 2 SharePoint management sites.

1. Launch “inetmgr” from the command prompt and open the “Web Sites”node.

2. Stop all sites except for the main one. In my case the main is “SharePoint - 80”

3. You may need to reboot the machine to insure the other w3wp.exe processes die. “iisreset” did not work. Perhaps “net stop w3svc” and then “net start w3svc” will work, but I haven’t tried.

4. Once you have only one w3wp.exe running you should build your project.

5. Load up the web part once in the SharePoint page by visiting the page, to get it to load the version that you just built.

6. Set a breakpoint inside the web part constructor or RenderWebPart method.

7. Now attach to the w3wp.exe process in Visual Studio 2005 by choosing: “Debug->Attach to Process” and selecting the w3wp.exe process in the dialog and click Attach. The first time you do this it may take a while to load up all the debug files. In addition if you see the breakpoint turn hollow with a little yield sign that says something to the effect of “The breakpoint will currently not be hit. No symbols have been loaded”, can mean that the web page did not properly load your web part. Try refreshing the page and reattaching the VS2005.

Briefly examining the TagFilter web part

So now that we understand the inner workings of the SharePoint Post web part, we now need to create our own web part that displays tags based on what posts are displayed and to construct the requesting URLs for those tags properly.

The TagFilter web part inherits from Microsoft.SharePoint.WebPartPages.WebPart which in turn inherits from System.Web.UI.WebControls.WebParts.WebPart which makes it very nice for developers that are familiar with the ASP.NET WebPart programming model. It also abstracts away the implementations of the System.Web.UI.INamingContainer, System.Web.UI.IAttributeAccessor and Microsoft.SharePoint.WebPartPages.IConnectionData Interfaces.

[ToolboxData("<{0}:TagFilter runat=server></{0}:TagFilter>")]

[XmlRoot(Namespace = "UberdemoWebparts")]

public class TagFilter : WebPart

{…}

Each web part must contain a RenderWebPart override which renders any controls that you want the webpart to display. At its simplest we could send “<div>Hello World</div>” down to the browser with:

protected override void RenderWebPart(HtmlTextWriter output)

{

   HtmlGenericControl container = new HtmlGenericControl("div");

   container.Controls.Add(new LiteralControl("Hello World"));

   container.RenderControl(output);

}

The most important thing to understand about this particular web part is how it accesses the Posts list and Categories that are contained within that list:

SPListCollection lists = SPControl.GetContextWeb(this.Context).Lists;

foreach (SPList list in lists)

{

   foreach (SPListItem item in list.Items)

   {

string[] tagArray = item.GetFormattedValue("Category").Trim().Split(';');

   }

}

 

Custom properties are very useful and only require you to define some special attributes in order to surface these properties in the web based web part page editor. Properties are persisted in the page CAML when they are modified.

[Bindable(true),

 Category("TagCloud Filter Settings"),

 DefaultValue(""),

 Description("Background Color for Tag Cloud"),

 FriendlyName("Background Color")]

public string TagCloudBackgroundColor

{

   get { return tagFilterBackgroundColor; }

   set { tagFilterBackgroundColor = value; }

}

The rest from here is straightforward business logic. There is a Generics Tags List and a Tag Class that contains the list of remaining tags for a selection. I’ve also created a StyleGrade List to manage Style settings that can be set in the web part properties. I also created a breadcrumb and added filtering logic. This web part was coded to work only with the Category.aspx page.

 

Here is the project in a zip file (14Kb)

 

Hopefully this has provided some hard to find educational value above and beyond the articles listed in the following resource section.

 

Other SharePoint Resources

MSDN

SharePoint Server 2007 Developer Portal

 

Web Part Development

1) A developers introduction to web parts

2) Walkthrough Creating a Basic SharePoint Web Part

3) Microsoft Windows SharePoint Services and Code Access Security

4) Configuring IntelliSense with CAML Files When Developing for Windows SharePoint Services 3.0

Web part Solutions and Samples

www.codeplex.com/cks, TagCloud 1.0 contains a generic Tag Cloud part and 3 other web parts.