The other day I wrote a quick web part for our corporate intranet which shows a list of all sites that the current user is a member of. The part is a bit slow, because it traverses the site hierarchy and checks to see if the current user is a member of the "Members" group of each site, but it works (for the most part). I just came across a blog posting titled "Increased performance for MOSS apps using the PortalSiteMapProvider" at http://blogs.msdn.com/ecm/archive/2007/05/23/increased-performance-for-moss-apps-using-the-portalsitemapprovider.aspx that I'll probably try to use to improve the performance of this web part at some point, but don't have time right now. I'm doing this blog post in order to share a few techniques I used to create the web part, and also give a link to a zip file of the web part Visual Studio project I created (yep… I'm finally giving back to the SharePoint community after taking-taking-taking).
I've zipped and uploaded the Visual Studio solution to my Office Live Site site download page, and the file is named Statera.Moss.Webparts.MyMemberships.img (NOTE: after you download the file, you need to change the extension to .ZIP and then you will be able to unzip it).
There's not very much code to this project at all, but there are a few things of interest above and beyond the purpose of the web part, so I'll walk through a few things here.
Overview
- All of the code is in the two c# files within the Source Directory
- The "MyMemberships.cs" file is the code for the web part
- The "MyMembershipsHelper.cs" file is the code for the MyMembershipsHelper class which is used as an ObjectDataSource within the web part so that I can make use of all the great features that the SPGridView server component.
-
Here's what the part looks like:
Making good use of the SPGridView
The SPGridView is a great SharePoint class which inherits from the .NET 2.0 GridView class and adds in all of the necessary css classes and other style stuff so that the grid looks like the other grids in SharePoint. I found a good posting about using the SPGridView at http://blogs.msdn.com/powlo/archive/2007/02/25/displaying-custom-data-through-sharepoint-lists-using-spgridview-and-spmenufield.aspx. The great thing about using the SPGridView (or even just the .NET 2.0 GridView class) is that you don't have to write any paging or sorting code if you get it all hooked up correctly.
Hooking the SPGridView up correctly within a web part is a bit tricky since you don't have use of the designer in Visual Studio. If you look at the code you'll notice a couple of things. First, the MyMembershipHelper class is extremely small… so small that I'll post its entire contents here:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Specialized;
using Microsoft.SharePoint;
namespace Statera.Moss.Webparts
{
public class MyMembershipsHelper
{
DataTable _myData;
public MyMembershipsHelper()
{
_myData = new DataTable();
}
public MyMembershipsHelper(DataTable myData)
{
_myData = myData;
}
public DataView GetQueryResults()
{
DataView myView = new DataView(_myData, "", "sitename", DataViewRowState.CurrentRows);
return myView;
}
}
}
All this class does is provide a place to store a DataTable as well as a method that the SPGridView can call when it needs the data (GetQueryResults). Now if you look at the MyMemberships cless, you'll see that during the initialization of the Web Part that an ObjectDataSource object is created and added to the web part's controls:
//-----------------------------
// Create the ObjectDataSource object
_myObjectDataSource = new ObjectDataSource("Statera.Moss.Webparts.MyMembershipsHelper", "GetQueryResults");
_myObjectDataSource.ID = "_myObjectDataSource";
_myObjectDataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(_myObjectDataSource_ObjectCreating);
this.Controls.Add(_myObjectDataSource);
By doing this, you can then set the DataSourceID of the SPGridView object to this new ObjectDataSource and the SPGridView will know where to get its data:
_grdResults = new SPGridView();
_grdResults.ID = "_grdResults";
_grdResults.EnableViewState = false;
_grdResults.DataSourceID = "_myObjectDataSource";
The only other thing you need to do is create the _myObjectDataSource_ObjectCreating method so that you can set the object instance of the ObjectDataSource to the instance of the myMembershipsQueryHelper class that you'll later create and populate:
public void _myObjectDataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = _myMembershipsQueryHelper;
}
Now that we have the SPGridView and ObjectDataSource objects all linked up, we just need to create an instance of the myMemberships class populate it with the sites that the current user is a member of.
Determining Site Membership
The only other interesting methods are the code is the GetMyMembershipsHelperClass() and the AddSite() methods. The GetMyMembershipsHelperClass() method creates the DataTable in order to store the membership information and then calls the AddSite() method. The AddSite() method is a recursive method (calls itself) in order to traverse the site hierarchy. Probably the most important line of code in the AddSite() method is:
string sIdOfMemberGroup = mySpWeb.Properties["vti_associatemembergroup"];
This line of code gets the ID of the "Members Group" so that we can later determine if the current user is a member of this group. The ID of the "Members Group" is stored in the Properties bag of the SPWeb when the site is created and a Members Group is created. NOTE: you can set the value of this property programmatically if you ever have the need to change it or set it to be a different group than the one that SharePoint sets up for you.
Now that we have the Group ID of the "Members Group" for the site, we can determine if the current user is in this group with the following code:
SPGroup myMemberGroup = mySpWeb.Groups.GetByID(iGroupId);
if (myMemberGroup.ContainsCurrentUser)
{
string sSiteUrl = mySpWeb.Url;
string sSiteDescription = mySpWeb.Description;
DataRow myNewRow = mySites.NewRow();
myNewRow["sitename"] = "<li>" + sSiteName + " </li>";
myNewRow["siteurl"] = sSiteUrl;
myNewRow["description"] = sSiteDescription;
mySites.Rows.Add(myNewRow);
mySites.AcceptChanges();
}
I had to put this code inside a try/catch block in order to catch the Access Denied exceptions that occur if the current user doesn't have enough permission to enumerate the Members Group. This is one place where performance could be drastically improved.
Deploying the SharePoint Solution
So, in addition to writing the code for the web part, I also setup the Visual Studio solution so that a SharePoint solution (.wsp) file would be created for easy deployment to SharePoint whenever you run a build. This was accomplished by doing the following:
- Create a manifest.xml file
- Create a DDF file for the makecab utility (Statêra.Moss.Webparts.MyMemberships.ddf)
- Putting the following two lines in the post build event command line (Build Even tab of the project properties):
cd $(ProjectDir)
makecab /f Statera.Moss.Webparts.MyMemberships.ddf
Now, every time I run a build the Statêra.Moss.Webparts.MyMemberships.wsp file gets created.
To add and deploy this solution to a WSS 3.0 or SharePoint 2007 web, you just copy the wsp file to the SharePoint server and then execute the following STSADM commands from the command line:
stsadm –o addsolution –filename Statera.Moss.Webparts.MyMemberships.wsp
stsadm –o deploysolution –name Statera.Moss.Webparts.MyMemberships.wsp –immediate –allcontenturls
NOTE: for some reason, if you copy and paste the above two lines into a command prompt, it won't work. You need to type them out by hand.
Well… that's all I've got to say at this time. Feel free to let me know if you have any problems…
Other key words or phrases: Membership Web Part MOSS 2007 WSS 3.0 Windows SharePoint Services 3.0