Microsoft Windows SharePoint Services Inside Out

 

This chapter is taken from Microsoft Windows SharePoint Services Inside Out published by Microsoft Press; ISBN 0735621713; copyright Microsoft Press 2005; all rights reserved. The author, Jim Buyens, has written more than 10 books on Web-based development, including Microsoft Office FrontPage 2003 Inside Out from Microsoft Press. Jim has 20 years of experience with computer networking technologies and is a Microsoft Most Valuable Professional who contributes extensively to the Microsoft Office FrontPage online communities.

No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any meanselectronic, electrostatic, mechanical, photocopying, recording or otherwisewithout the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Chapter 21: Creating Custom Administration Tools

Contents

Accessing Administrative Methods
Creating the TopSites Web Part
In Summary

Chapters 15, 16, and 17 explained the many ways you can administer Microsoft Windows SharePoint Services at the site, site collection, virtual server, and server farm level. Inevitably, however, some organizations will prefer to design their own administrative interfaces. For example, they may wish to reduce common operations from multiple screens to one, or to synchronize member lists and other information with external systems.

In cases like these, Windows SharePoint Services comes to the rescue with a rich set of administration objects you can access from custom Web Parts or, for that matter, from any Windows program. This chapter will introduce those objects, and then show how to develop a custom Web Part that performs two simple administrative tasks: displaying and creating the top-level sites on a SharePoint server.

Why create a Web Part like this when you can use the Create Top-level Web Site page on the Central Administration server to do the same job? Here are three common reasons:

  • The Create Top-Level Web Site page may have more options than you want the person who creates top-level sites to have. In other words, you want to reduce the chances of making a mistake.
  • You want to automate certain corporate policies. Suppose, for example, that the same username string should appear in all logon accounts, e-mail addresses, and top-level site URLs. You could program the Web Part to display a single text box and construct the logon account, e-mail address, and top-level site URL programmatically.
  • The Web Part displays a list of existing top-level sites. Windows SharePoint Services provides no Web Page that displays this information.

For more information about the Create Top-level Web Site page, refer to "Creating a Top-Level Site as an Administrator" in Chapter 5.

Accessing Administrative Methods

The Microsoft.SharePoint.Administration namespace provides a rich array of objects you can manipulate to manage a SharePoint server or server farm. This is where you should look if you want to write your own Web Parts or stand-alone programs that perform administrative tasks, and if the Microsoft.SharePoint namespace doesn't have the properties or methods you need.

The most important classes in the Microsoft.SharePoint.Administration namespace are:

  • SPGlobalAdmin   This class provides access to all server-level settings in a SharePoint deployment. It provides methods, for example, to create and delete central administration servers, create and delete configuration databases, and extend and unextend virtual servers.
  • SPVirtualServer   This class provides access to virtual server settings, such as its collection of top-level sites, its collection of content databases, its e-mail settings, and so forth.

To create an SPGlobalAdmin object, you add a using statement such as this to your source file:

using Microsoft.SharePoint.Administration;

and then code a statement like this within any convenient method:

SPGlobalAdmin spgAdmin = new SPGlobalAdmin();

After you do this, the following expressions would, for example, return the IP address and name of the first front-end Web server using the current configuration database.

spgAdmin.Config.WebServers[0].Address 
spgAdmin.Config.WebServers[0].Name

There are three ways of getting an SPVirtualServer object. Choose the one that best suits your application.

  • Use the VirtualServers property of the SPGlobalAdmin class to get a collection of SPVirtualServer objects: one for each virtual server on the current computer or in the current farm. The following code, for example, creates an SPVirtualServer object that represents the first virtual server.

    SPGlobalAdmin spgAdmin = new SPGlobalAdmin(); 
    SPVirtualServer spvServer = spgAdmin.VirtualServers[0];
    

    And the following code iterates through the entire collection of virtual servers:

    foreach (SPVirtualServer spvServer in spgAdmin.VirtualServers) 
    {
    //  Code to handle each virtual server would go here. 
    }
    
  • Use the VirtualServer property of the SPSiteCollection class.

    All SPVirtualServer objects have a Sites property that points to an SPSiteCollection object. That object, in turn, has a VirtualServer property that points back to the SPVirtualServer.

    This might seem a bit redundant, but if all you have is the SPSiteCollection object, it is good to know you can get back to the virtual server if the need arises.

  • Use the VirtualServer property of the SPVirtualServerConfig class.

    Again, this is circular. An SPVirtualServer object has a Config property that points to an SPVirtualServerConfig object, and that object has a VirtualServer property that points back to the virtual server. It is there if you need it.

Starting from the SPGlobalAdmin and SPVirtualServer objects, you can retrieve or modify almost any aspect of a SharePoint server or server farm. For example, the rest of this chapter will describe a Web Part that uses these objects to display and create top-level Web sites.

When you write a custom program to administer Windows SharePoint Services, the program must run under an account capable of performing the same actions using the SharePoint Central Administration pages, Top-Level Site Administration pages, or Site Administration pages.

  • In the case of a Web Part, Windows SharePoint Services will display a new login prompt if the team member running the Web Part doesn't have enough privileges.
  • In the case of a batch or command-line program, Windows SharePoint Services will raise an exception if the login account lacks the necessary permissions.

For more information about the SPGlobalAdmin object, the SPVirtualServer object, and all their children, see Microsoft.SharePoint.Administration Namespace.

Creating the TopSites Web Part

To illustrate some aspects of programmatically administering a SharePoint site, this section explains how to write a Web Part that displays and adds top-level Web sites on a virtual server. This is the Web Part that appears in Figure 21-1.

An administrator can use the Web Part in this page to display and create top-level sites

Figure 21-1. An administrator can use the Web Part in this page to display and create top-level sites

For simplicity, this Web Part always administers the first SharePoint content server on the computer where the Web Part is running. To create a top-level site, an administrator fills in the Top Site, Owner Login, and Owner E-Mail boxes, and then clicks the Add button.

Initializing the WssIsoAdmin Project

To create a Microsoft Visual Studio .NET project for the TopSites Web Part, proceed as follows.

  1. Start Visual Studio and create a new project named WssIsoAdmin. Specify the Web Part Library templates just as you did for the WssIso project in Chapter 20.
  2. When the WebPart1.cs file appears in Visual Studio, rename it to TopSites.cs and then, within the file, change all occurrences of WebPart1 to TopSites.

Alternatively, choose Add New Item from the Project menu and use the Web Part template to create a class named TopSites.cs.

Once you have a TopSites.cs file, delete or resolve to ignore the template code for the Text custom property, and the commented code for custom tool parts. This Web Part won't use either of those features.

You can find a copy of the completed WssIsoAdmin project on the Companion CD, in the \Webparts\WssIsoAdmin folder. To install this project on your system, copy the WssIsoAdmin folder into your Visual Studio Projects folder, clear the Read-Only attributes, and then open the WssIsoAdmin.sln file.

Declaring Namespaces and Class Variables

The tasks in this section provide access to classes in two namespaces that the Web Part template doesn't include by default, and they declare variables used in more than one method. Proceed as follows.

  1. Add the following statements just after the using statements that the template provides.

    using System.Web.UI.HtmlControls; 
    using Microsoft.SharePoint.Administration;
    

    The System.Web.UI.HtmlControls namespace provides classes for the HTML tables and paragraphs in the user interface. The Microsoft.SharePoint.Administration namespace provides objects for inspecting and modifying SharePoint sites.

  2. Declare the following variables within the TopSites class, but not within any other method or structure.

    SPGlobalAdmin spgAdmin; 
    SPVirtualServer spvServer; 
    SPSiteCollection sscTopLvs; 
    HtmlGenericControl parMsg; 
    TextBox txtPath; 
    TextBox txtLogin; 
    TextBox txtEmail; 
    HtmlInputHidden hidRepost;
    

    The spgAdmin variable will point to an SPGlobalAdmin object that provides entry to the administration functions. The spvServer and SPSiteCollection variables will point to objects that represent the virtual server and its list of site collections.

    The parMsg variable will point to an HTML paragraph that displays any error messages. The txtPath, txtLogin, and txtEmail variables will identify the three text boxes shown in Figure 21-1. The hidRepost variable will point to a hidden form field that differentiates between initial display and subsequent postback.

Overriding the CreateChildControls Method

Like the Web Parts in Chapter 20, Web Parts that perform administrative tasks must override the CreateChildControls method in order to create output controls at the proper time. To perform this task for this Web Part, proceed as follows.

  1. Override the CreateChildControls method, just as you did in the previous chapter. In other words, add the following code within the TopSites class but outside any other method or structure. This code also creates the SPGlobalAdmin object.

    protected override void CreateChildControls () 
    {
        spgAdmin = new SPGlobalAdmin(); 
    }
    
  2. Search the SPGlobalAdmin object's VirtualServers collection, looking for the first administrable server. When you find it:

    • Save a pointer named spvServer that points to the virtual server.
    • Save a pointer named sscTopLvs that points to the server's list of site collections.
    • Break out of the loop.

    This requires the code shown below in bold.

    spgAdmin = new SPGlobalAdmin(); 
    foreach (SPVirtualServer spvSrv in spgAdmin.VirtualServers) {    if (spvSrv.State == SPVirtualServerState.Ready)         {        spvServer = spvSrv;        sscTopLvs = spvServer.Sites;        break;    }}
    

    SPVirtualServerState.Ready is a public constant that means the server is extended with Windows SharePoint Services, that it is running, and that it is not a SharePoint Central Administration server. Table 21-1 lists all the possible SPVirtualServerState values.

    Table 21-1. SPVirtualServerState values

    Attribute Purpose
    Browsable A value of false stops the custom property from appearing in the Web Part's property sheet The default is true. Setting the WebPartStorage attribute to Storage.None has the same effect.
    Category The section title where the custom property will appear on the property sheet. If you leave this attribute empty or set it to Default, Appearance, Layout, or Advanced, the custom property will appear in the Miscellaneous section.
    Description The tool tip text that appears if a team member pauses the mouse pointer over the custom property's input control.
    DefaultValue The custom property's default value.
    FriendlyNameAttribute The caption or title that identifies the custom property. If you leave this empty, the program name for the property will appear.
    ReadOnly A value of true makes the custom property read-only in the property sheet. The default is false.
    WebPartStorage The view modes for which Windows SharePoint Services will display and save custom property values. The permissible values are:
    • Storage.Shared   Only when configuring the Web Part's shared view.
    • Storage.Personal   When configuring either the shared or personal view.
    • Storage.None   Never.
    HtmlDesignerAttribute Associates a property builder (that is, a custom Web page or module) with the property. This overrides the normal input format in Web page designers such as Microsoft Office FrontPage 2003.
  3. Create a generic HTML paragraph and use it to display the virtual server's URL. This requires the following code, next in sequence:

    HtmlGenericControl parSite = new HtmlGenericControl("p");
    parSite.InnerHtml = "Server: " + spvServer.Url.ToString(); 
    parSite.Style.Add("margin","3px"); 
    Controls.Add(parSite);
    

    Notice that spvServer is the variable where step 2 stored the SPVirtualServer object that points to the first administrable virtual server. The Url property points to a System.Uri object that provides a variety of information about the server's URL.

  4. To start displaying the input form, create an HTML table, add a new row, add four new cells, and store heading text in the first three of those cells. Here is the necessary code:

    HtmlTable tblForm = new HtmlTable(); 
    tblForm.Rows.Add(new HtmlTableRow()); 
    tblForm.Rows[0].Cells.Add(new HtmlTableCell()); 
    tblForm.Rows[0].Cells.Add(new HtmlTableCell()); 
    tblForm.Rows[0].Cells.Add(new HtmlTableCell());
    tblForm.Rows[0].Cells.Add(new HtmlTableCell()); 
    tblForm.Rows[0].Cells[0].InnerText = "Top Site"; 
    tblForm.Rows[0].Cells[1].InnerText = "Owner Login"; 
    tblForm.Rows[0].Cells[2].InnerText = "Owner E-Mail";
    
  5. To prepare for the form fields, add another row and another four cells to the same table. This requires the following code, next in sequence:

    tblForm.Rows.Add(new HtmlTableRow()); 
    tblForm.Rows[1].Cells.Add(new HtmlTableCell());
    tblForm.Rows[1].Cells.Add(new HtmlTableCell()); 
    tblForm.Rows[1].Cells.Add(new HtmlTableCell()); 
    tblForm.Rows[1].Cells.Add(new HtmlTableCell());
    
  6. Instantiate the three text boxes and add them to the first three cells from step 5. In other words, append the following code:

    txtPath = new TextBox(); 
    tblForm.Rows[1].Cells[0].Controls.Add(txtPath);  
    
    txtLogin = new TextBox(); 
    tblForm.Rows[1].Cells[1].Controls.Add(txtLogin);  
    
    txtEmail = new TextBox(); 
    tblForm.Rows[1].Cells[2].Controls.Add(txtEmail);
    
  7. Instantiate the form button, give it a caption of Add, and assign an event handler named BtnAddClick to its Click event. Then, after adding the button to the fourth table cell, add the entire table to the Web Part's Controls collection.

    Button btnAdd = new Button(); 
    btnAdd.Text = "Add"; 
    btnAdd.Click += new EventHandler(BtnAddClick); 
    tblForm.Rows[1].Cells[3].Controls.Add(btnAdd);  
    
    Controls.Add(tblForm);
    
  8. Create another generic paragraph, this time for any error messages that occur. Set its EnableViewState property to false so that Microsoft ASP.NET doesn't restore the error message the next time the Web Part runs. Assign the standard SharePoint CSS class ms-error, override this with a paragraph margin of three pixels, and initialize the content to a nonbreaking space. Finally, add the paragraph to the Web Part's Controls collection.

    parMsg = new HtmlGenericControl("p"); 
    parMsg.EnableViewState = false; 
    parMsg.Attributes["class"] = "ms-error"; 
    parMsg.Style.Add("margin", "3px"); 
    parMsg.InnerHtml = " "; 
    Controls.Add(parMsg);
    
  9. Create a hidden form field named hidRepost, make sure its EnableViewState property is true, and assign an event hander named RepostLoaded for the Load event. Then, of course, add it to the Web Part's Controls collection.

    hidRepost = new HtmlInputHidden(); 
    hidRepost.EnableViewState = true; 
    hidRepost.Load += new EventHandler(RepostLoaded); 
    Controls.Add(hidRepost);
    

This completes the coding for the CreateChildControls method.

Writing the RepostLoaded Event Handler

The Web Part needs to generate the list of existing top-level sites in two distinctly different situations:

  • When the page runs for the first time.
  • Whenever the team member clicks the Add button.

The TopSites Web Part uses a hidden form field to detect when it runs for the first time. On the first execution this field will, by default, contain an empty string. When the code finds this empty value, it changes it to a y and then displays the list of top-level sites. On subsequent executions, the value of y keeps the Web Part from reloading the list of sites.

Note   Web Parts, like any other ASP.NET control, use a multithreaded event model. This means that ASP.NET starts methods within the Web Part as soon as they are ready to run, and in no particular order. This improves the overall performance of the server.

Unfortunately, reading the values of hidden form fields (or, for that matter, any kind of form fields) can be tricky. When you create a control in the CreateChildControls method, you can't depend on values from the browser being immediately present. It may take some time for a parallel process to insert the form field value received from the browser.

This is why step 9, in the previous section, appended an event hander to the hidden form field's Load event. The Load event doesn't fire until the control is fully loaded, which includes receiving its value from the browser.

Fortunately, the code for this event handler is much simpler than the explanation. The complete code listing appears below.

void RepostLoaded(object sender, EventArgs e) 
{
   if (hidRepost.Value != "y")     
   {
      hidRepost.Value = "y";
      ListSites();     
   }
}

The name of the event hander agrees with the name you specified in step 9 of the previous section. If the form field's value isn't y, the code sets it to y and runs a method named ListSites. This is the method that displays the current list of top-level sites.

Insert this code anywhere within the TopSites class, but not within any other method or structure.

Writing the BtnAddClick Event Handler

The code that created the Web Part's Button object specified that clicking the button should run an event handler named BtnAddClick. To develop this event handler, proceed as follows:

  1. Add the following method declaration anywhere within the TopSites class, but not within any other method or structure.

    void BtnAddClick(object sender, EventArgs e) 
    { 
    }
    
  2. Between the curly braces, call the EnsureChildControls method to ensure that the CreateChildControls method has completed and that all children of the Web Part's Controls collection have finished loading. Then, code a try / catch block. This requires the statements shown below in bold:

    void BtnAddClick(object sender, EventArgs e) 
    {     
       EnsureChildControls();try    {}catch(Exception ex)     {     } 
    }
    
  3. Add the code shown below in bold to the try block, the catch block, and the end of the method.

    void BtnAddClick(object sender, EventArgs e) 
    {
       EnsureChildControls();
       try
       {
          SPSite newSiteCollection =sscTopLvs.Add(txtPath.Text, txtLogin.Text, txtEmail.Text);
       }     
       catch(Exception ex)     
       { 
             parMsg.InnerText = ex.Message;    
       }     
       ListSites(); 
    }
    

    The code in the try block creates a new top-level site at the given path, with the given owner login account, and with the given owner e-mail address. This is the simplest form of the Add method for an SPSiteCollection object.

    If Windows SharePoint Services can't create the new top-level site, it raises an exception, and the code in the catch block runs. That code retrieves the error message from the exception and displays it in the parMsg control.

    The last statement runs the ListSites method, just as the RepostLoaded method did. This displays a current listing of top-level sites.

This completes the code for the BtnAddClick event handler.

Adding Top-Level Web Sites

The SPSiteCollection class has several overloaded methods, all named Add, and each accepts a different combination of arguments. The TopSites Web Part uses the simplest form, which specifies only the top-level site's URL, primary owner account, and primary owner e-mail address.

By calling the Add method with different signatures, you can also specify the Web site title, description, locale identifier, site definition or site template, primary owner display name, secondary owner account, display name, and e-mail address, the name of a new database, and the user name and password of the database administrator.

For more information, see SPSiteCollection Class.

Writing the ListSites Method

Both the RepostLoaded event handler and the BtnAddClick event handler call this method to display a current listing of top-level sites. To code the method, proceed as follows:

  1. Declare a method named ListSites. As usual, locate this anywhere within the TopSite class, but not within any other method or structure. Here is the code:

    void ListSites () 
    { 
    }
    
  2. Within the curly braces from step 1, declare an HtmlTable variable named tblSites and an HtmlTableRow named rowSites.

    void ListSites () 
    {
       HtmlTable tblSites;HtmlTableRow rowSites; 
    }
    
  3. Instantiate the HtmlTable object. Then, to avoid sending large amounts of ViewState data to the browser (and receiving it back), set its EnableViewState property to false. This requires the code shown below in bold.

    HtmlTable tblSites; 
    HtmlTableRow rowSites;
    
    tblSites = new HtmlTable(); tblSites.EnableViewState = false;
    
  4. Add a table row to the tblSites table, set a variable that points to it, and set its class attribute to ms-vh. This is the usual CSS class for SharePoint table headings.

    tblSites.Rows.Add(new HtmlTableRow()); 
    rowSites = tblSites.Rows[tblSites.Rows.Count - 1]; 
    rowSites.Attributes["class"] = "ms-vh";
    
  5. Add six cells to the row, and then store the six column headings in them.

    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells.Add(new HtmlTableCell()); 
    rowSites.Cells[0].InnerText = "Top Site"; 
    rowSites.Cells[1].InnerText = "Title"; 
    rowSites.Cells[2].InnerText = "Owner Name"; 
    rowSites.Cells[3].InnerText = "Owner Login"; 
    rowSites.Cells[4].InnerText = "Owner E-Mail"; 
    rowSites.Cells[5].InnerText = "Last Modified";
    
  6. Set up a loop that iterates through each member of the Names collection in the current virtual server's ** SPSiteCollection object. The sscTopLvs variable points to this object. Here is the code:

    foreach (string strTopUrl in sscTopLvs.Names) 
    { 
    }
    

    Each item in the Names collection contains the relative URL of one top-level site.

  7. VirtualServer objects have a MakeFullUrl method that converts a relative URL such as sites/buyensj to a fully qualified URL such as http://wish.interlacken/com/sites/buyensj.

    Using this method, get the fully qualified URL of the current top-level site, and then use the result to create an SPSite object named spsSiteColl. This requires the code shown below in bold.

    foreach (string strTopUrl in sscTopLvs.Names) 
    {
       SPSite spsSiteColl = newSPSite(sscTopLvs.VirtualServer.MakeFullUrl(strTopUrl)); 
    }
    
  8. Add a new row to the table you created in step 3, and then add six cells to that row. Here is the required code in bold:

    foreach (string strTopUrl in sscTopLvs.Names) 
    {
       SPSite spsSiteColl = new
             SPSite(sscTopLvs.VirtualServer.MakeFullUrl(strTopUrl)); 
       tblSites.Rows.Add(new HtmlTableRow());rowSites = tblSites.Rows[tblSites.Rows.Count - 1];rowSites.Cells.Add(new HtmlTableCell());rowSites.Cells.Add(new HtmlTableCell());rowSites.Cells.Add(new HtmlTableCell());rowSites.Cells.Add(new HtmlTableCell());rowSites.Cells.Add(new HtmlTableCell());rowSites.Cells.Add(new HtmlTableCell()); 
    }
    
  9. For each of the six table cells you created in step 8, set the valign= attribute to top. This requires the following code, next in sequence.

    foreach (HtmlTableCell celSites in rowSites.Cells) 
    {
       celSites.VAlign = "top"; 
    }
    
  10. Create an HtmlAnchor object (that is, a hyperlink). If the current top-level site's URL is empty, make the HtmlAnchor object appear (root); otherwise, make it display the site's relative URL. Set the hyperlink's Href property to the fully qualified version of the top-level site's URL, and then add the HtmlAnchor object to the first table cell's Controls collection. Append this code to that from the previous step.

    HtmlAnchor ancTopUrl = new HtmlAnchor(); 
    if (strTopUrl == "") 
    {
       ancTopUrl.InnerText = "(root)"; 
    }
    else
    {
       ancTopUrl.InnerText = strTopUrl; 
    }
    ancTopUrl.HRef = spvServer.MakeFullUrl(strTopUrl);
    rowSites.Cells[0].Controls.Add(ancTopUrl);
    
  11. Load table cells 2 through 6 with their respective property values. Here is the code:

    rowSites.Cells[1].InnerText = spsSiteColl.RootWeb.Title; 
    rowSites.Cells[0].Controls.Add(ancTopUrl); 
    rowSites.Cells[2].InnerText = spsSiteColl.Owner.Name; 
    rowSites.Cells[3].InnerText = spsSiteColl.Owner.LoginName; 
    rowSites.Cells[4].InnerText = spsSiteColl.Owner.Email; 
    rowSites.Cells[5].InnerText = 
       spsSiteColl.LastContentModifiedDate.ToLocalTime().ToString();
    

    Note that the LastContentModifiedDate will appear by default as a UTC time. The ToLocalTime method converts this to the server's local time zone.

  12. Add the tblSites table to the Web Part's Controls collection. Make sure this statement is within the ListSites method, but not within the loop that iterates through the ss-cTopLvs.Names collection.

    Controls.Add(tblSites);
    

This completes the code for the ListSites method. A complete listing appears below.

void ListSites ()
{
   HtmlTable tblSites;
   HtmlTableRow rowSites;
   
   tblSites = new HtmlTable();
   tblSites.EnableViewState = false;
   tblSites.Rows.Add(new HtmlTableRow());
   rowSites = tblSites.Rows[tblSites.Rows.Count - 1];
   rowSites.Attributes["class"] = "ms-vh";
   rowSites.Cells.Add(new HtmlTableCell());
   rowSites.Cells.Add(new HtmlTableCell());
   rowSites.Cells.Add(new HtmlTableCell());
   rowSites.Cells.Add(new HtmlTableCell());
   rowSites.Cells.Add(new HtmlTableCell());    
   rowSites.Cells.Add(new HtmlTableCell());     
   rowSites.Cells[0].InnerText = "Top Site";     
   rowSites.Cells[1].InnerText = "Title";    
   rowSites.Cells[2].InnerText = "Owner Name";     
   rowSites.Cells[3].InnerText = "Owner Login";     
   rowSites.Cells[4].InnerText = "Owner E-Mail";     
   rowSites.Cells[5].InnerText = "Last Modified";      

   foreach (string strTopUrl in sscTopLvs.Names)     
   {         
      SPSite spsSiteColl = new
         SPSite(sscTopLvs.VirtualServer.MakeFullUrl(strTopUrl));
      tblSites.Rows.Add(new HtmlTableRow());         
      rowSites = tblSites.Rows[tblSites.Rows.Count - 1];         
      rowSites.Cells.Add(new HtmlTableCell());         
      rowSites.Cells.Add(new HtmlTableCell());         
      rowSites.Cells.Add(new HtmlTableCell());         
      rowSites.Cells.Add(new HtmlTableCell());         
      rowSites.Cells.Add(new HtmlTableCell());         
      rowSites.Cells.Add(new HtmlTableCell());         
      foreach (HtmlTableCell celSites in rowSites.Cells)         
      {
      celSites.VAlign = "top";         
      }         
      HtmlAnchor ancTopUrl = new HtmlAnchor();         
      if (strTopUrl == "")         
      {
      ancTopUrl.InnerText = "(root)";         
      }         
      else
      {
         ancTopUrl.InnerText = strTopUrl;         
      }
      ancTopUrl.HRef = spvServer.MakeFullUrl(strTopUrl);
      rowSites.Cells[0].Controls.Add(ancTopUrl);         
      rowSites.Cells[1].InnerText = spsSiteColl.RootWeb.Title;         
      rowSites.Cells[2].InnerText = spsSiteColl.Owner.Name;         
      rowSites.Cells[3].InnerText = spsSiteColl.Owner.LoginName;         
      rowSites.Cells[4].InnerText = spsSiteColl.Owner.Email;         
      rowSites.Cells[5].InnerText =     
   spsSiteColl.LastContentModifiedDate.ToLocalTime().ToString();     
   }
   Controls.Add(tblSites); 
}

Overriding the RenderWebPart Method

Find the version of the RenderWebPart method that the Web Part template provides, and change it to read as follows.

protected override void RenderWebPart(HtmlTextWriter output) 
{
   RenderChildren(output); 
}

This completes the coding for the Web Part.

Building, Installing, and Testing the Web Part

Compiling and testing the TopSites Web Part involves the same tasks you performed for the Web Parts in Chapter 20. Specifically:

  1. Use the sn.exe program to generate a strong key file. You need this so that Visual Studio .NET can give your assembly a strong name.

  2. Add the strong key file to your project, and enter its name in the AssemblyInfo.cs file.

  3. Choose Rebuild Solution from the Build menu and resolve any compilation errors. Repeat as necessary.

  4. Copy the resulting DLL into your server's global assembly cache, or into the bin folder for your virtual server.

  5. Add a <SafeControl> entry to the <SafeControls> section of the virtual server's web.config file.

    Remember that the InstallAssemblies tool can perform steps 4 and 5 for you.

  6. Make sure the Web Part appears on the Web Part Gallery: New Web Parts (AllItems.aspx) page. If so, select it and click Populate Gallery.

  7. Add the Web Part to a Web Part Page (probably a new one) and see what happens.

For more detail on the process of installing and testing a Web Part, refer to the section titled "Installing and Testing" in Chapter 20.

In Summary

This chapter introduced the high-level objects that Windows SharePoint Services provides for performing administrative tasks. It then explained how to use these objects to write a Web Part that displays the top-level sites on a virtual server and creates new top-level sites.

This book introduced and explained every major aspect of Windows SharePoint Services, including the browser interface, integration with the Microsoft Office System, advanced design with FrontPage 2003, installation on Microsoft Windows Server 2003, use with Microsoft SQL Server 2000, complete administration and configuration, and custom programming with Visual Studio .NET. This is an impressive range of topics, and it clearly indicates the strategic position Windows SharePoint Services occupies among Microsoft technologies. Hopefully, this book has magnified your understanding of this important technology and provided exceptional benefit to you and your organization. Good luck with your site, and I hope we meet again.