Introducing the ASP.NET 2.0 Web Parts Framework

 

Stephen Walther
Microsoft Corporation

March 2005

Applies to:
   Microsoft ASP.NET 2.0
   ASP.NET Framework
   Web Parts

Summary: Web Parts provide you with the infrastructure for creating customizable Web applications, such as intranet portal applications. When you build a Web site with Web Parts, the Web site can be easily customized by either the administrator of the Web site or the individual users of the Web site. Web Parts are all about flexibility. (53 printed pages)

Download MSDNWebPartsCS.msi. The installer will guide you through the steps required to install Introducing the ASP.NET 2.0 Web Part Framework (CS) on your computer.

Download MSDNWebPartsVB.msi. The installer will guide you through the steps required to install Introducing the ASP.NET 2.0 Web Part Framework (VB) on your computer.

Contents

Introduction
Building Web Parts
Personalizing Web Parts
Creating a Custom Web Part Menu
Communicating Information Between Web Parts
Importing and Exporting Web Part Settings
Conclusion

Introduction

By taking advantage of the new Web Parts framework introduced with ASP.NET 2.0, you can build applications that can be easily customized by users at runtime. Users can rearrange the elements of a page built with Web Parts by using a drag-and-drop interface. Users can also add and remove elements in a page by working with one or more Web Part catalogs.

This article introduces you to the Web Part framework. It also goes beyond the basics and discusses several advanced features of Web Parts. For example, you learn how to share Web Part settings across applications by importing and exporting Web parts and you learn how to add custom menu verbs to Web Parts. You also learn how to communicate information between Web Parts by connecting different Web Parts in a Web Page.

Building Web Parts

There are two basic ways to create a Web Part. You can treat any standard Microsoft ASP.NET control as a Web Part or you can build a custom control that derives from the base WebPart class.

You are not required to modify a control in any way to use it as a Web Part. Standard ASP.NET controls (such as the Calendar and GridView controls), Web User Controls, and even custom controls can all be used as Web Parts. When you treat a standard control as a Web part, the control is represented in the Web Part framework with the GenericWebPart class.

Alternatively, you can build a custom Web Part control by building a new control that derives from the base WebPart class. The advantage of this second method of creating a Web Part is that it provides you with additional options for customizing the appearance and behavior of the Web Part. In this article, since we'll tackle some advanced features of Web Parts, we'll build all of our Web Parts by creating custom Web Part controls.

Creating a Simple Web Part

Let's begin by creating a simple Web Part. We'll keep modifying this Web Part over the course of this article by adding new functionality to it.

Our Web Part will randomly display different products from the products database table. We'll name it the FeaturedProductPart1 control. The code for the control is contained in Listing 1.

Listing 1. FeaturedProductPart1.cs (C#)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
    public class FeaturedProductPart1 : WebPart
    {
        const string connectionString = 
      "Server=localhost;Trusted_Connection=True;Database=Northwind";
        const string selectString = "SELECT * FROM Products";
        public FeaturedProductPart1()
        {
            this.Title = "Featured Product";
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            // Load Products into DataTable
            DataTable productTable = new DataTable();
            SqlDataAdapter dad = new SqlDataAdapter(selectString, 
              connectionString);
            dad.Fill(productTable);
            // Randomly select a row
            Random rnd = new Random();
            DataRow row = 
              productTable.Rows[rnd.Next(productTable.Rows.Count)];
            // Render the row
            writer.Write((string)row["ProductName"]);
            writer.Write(String.Format("- {0:c}", row["UnitPrice"]));
        }
    }
}

Listing 1. FeaturedProductPart1.vb (Visual Basic .NET)

Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class FeaturedProductPart1
        Inherits WebPart
        Const connectionString As String = 
          "Server=localhost;Trusted_Connection=True;Database=Northwind"
        Const selectString As String = "SELECT * FROM Products"
        Sub New()
            MyBase.Title = "Featured Product"
        End Sub
        Protected Overrides Sub RenderContents(ByVal writer 
          As HtmlTextWriter)
            ' Load Products into DataTable
            Dim productTable As New DataTable()
            Dim dad As New SqlDataAdapter(selectString, 
              connectionString)
            dad.Fill(productTable)
            ' Randomly select a row
            Dim rnd As New Random()
            Dim row As DataRow = 
              productTable.Rows(rnd.Next(productTable.Rows.Count))
            ' Render the row
            writer.Write(row("ProductName").ToString())
            writer.Write(String.Format("- {0:c}", row("UnitPrice")))
        End Sub
    End Class
End Namespace

Notice that the control in Listing 1 derives from the base WebPart control. Since the control derives from the base WebPart control, the control automatically inherits all of the functionality of a Web Part.

The constructor assigns the value Featured Product to the Title property. The Title property appears in the title bar of the Web Part and within the Web Part Catalog (more on Web Part Catalogs in the next section).

Finally, just like a normal custom control, our Web Part control includes a RenderContents method. The RenderContents method determines what the Web Part will display when the Web Part is placed within a page. In the case of our FeaturedProductPart1 control, a product name and price is randomly retrieved from the Products database table and displayed.

Displaying Web Parts

The cool thing about Web Parts is that they enable you to customize the appearance of a page at runtime. In order to support this functionality, you must add some standard controls to any page that takes advantage of Web Parts.

Every page that contains Web Parts must include one, and only one, WebPartManager control. This control must be placed before (above) any other Web Part control in a page. The WebPartManager control is responsible for tracking information about the state of all of the Web Parts in the page.

In addition, any page which includes Web Parts must include one or more WebPartZone controls. These controls are used to mark the different areas of the page (Web Part Zones) to which Web Parts can be added. For example, you can drag-and-drop Web Parts between different Web Part Zones.

Finally, and this is optional, a page containing Web Parts can contain a CatalogZone control. This control is used to display one or more catalogs of Web Parts that you can add to a page.

The page in Listing 2 includes all three types of controls.

Listing 2. Home1.aspx (C#)

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    void CustomizePage(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = 
          WebPartManager.CatalogDisplayMode;
    }
        
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart1 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

Listing 2. Home1.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    Sub CustomizePage(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
    End Sub
        
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart1 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

When you first open the page in Listing 2, you see a page that contains three blank columns and a Customize Page link (see Figure 1). When you click the Customize Page link, the CustomizePage method executes and sets the DisplayMode to the value CatalogDisplayMode.

ms379628.webparts_fig01(en-US,VS.80).gif

Figure 1. Viewing the Home Page

When the page is set to CatalogDisplayMode, you can add new Web Parts to the page and customize the layout of existing Web Parts. The CatalogZone control displays both a Page Catalog and Declarative Catalog (see Figure 2).

ms379628.webparts_fig02(en-US,VS.80).gif

Figure 2. Adding Web Parts to a Page

The Declarative Catalog contains the FeaturedProductPart1 Web Part that we created in the previous section. You can add an instance of this Web Part from the Declarative Catalog to either of the two Web Part Zones in the page by clicking the Add button. After you add a Web Part to a Web Part Zone, you can drag-and-drop the Web Part between zones by dragging the Web Part with your mouse (see Figure 3).

ms379628.webparts_fig03(en-US,VS.80).gif

Figure 3. Moving a Web Part Between Web Part Zones

Notice that the FeaturedProductPart1 Web Part appears in the page with a Minimize and Close link. It also appears with a dropdown menu (you have to click the down arrow to open the menu), which contains a Delete menu option. If you close a Web Part, the Web Part is removed from the page and added to the Page Catalog. If you delete a Web Part, then that particular instance of the Web Part is permanently deleted from the page.

After you finish customizing the appearance of the page, you can click the Close link that appears over the Web Part Catalogs. Clicking the Close link hides the Web Part Catalogs and prevents you from customizing the page by dragging-and-dropping Web Parts between zones.

Personalizing Web Parts

The Featured Product Web Part that we created in the previous section was very simple. It does not include any properties. You can, however, build more complex Web Parts that include properties users can modify at runtime.

For example, it would be nice if the Featured Product control enabled you to randomly display products in a particular category. In this section, we will modify the control so that it supports a CategoryName property that the user can personalize.

The updated control is contained in Listing 3:

Listing 3. FeaturedProductPart2.cs (C#)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
    public class FeaturedProductPart2 : WebPart
    {
        const string connectionString = 
        "Server=localhost;Trusted_Connection=True;Database=Northwind";
        const string selectString = 
          "SELECT * FROM Products JOIN Categories ON " +
          "Products.CategoryID=Categories.CategoryID";
        private string _categoryName = "Beverages";
        public FeaturedProductPart2()
        {
            this.Title = "Featured Product";
        }
        [Personalizable, WebBrowsable]
        public string CategoryName
        {
            get { return _categoryName; }
            set { _categoryName = value; }
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            // Load Products into DataTable
            DataTable productTable = new DataTable();
            SqlDataAdapter dad = new SqlDataAdapter(selectString, 
              connectionString);
            dad.Fill(productTable);
            // Filter the DataTable with a Category
            DataView productView = productTable.DefaultView;
            productView.RowFilter = "CategoryName='" + 
              _categoryName + "'";
            if (productView.Count == 0)
                return;
            // Randomly select a row
            Random rnd = new Random();
            DataRowView row = productView[rnd.Next(productView.Count)];
            // Render the row
            writer.Write((string)row["ProductName"]);
            writer.Write( String.Format("- {0:c}", row["UnitPrice"]));
        }
    }
}

Listing 3. FeaturedProductPart2.vb (Visual Basic .NET)

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class FeaturedProductPart2
        Inherits WebPart
        Const connectionString As String = 
        "Server=localhost;Trusted_Connection=True;Database=Northwind"
        Const selectString As String = "SELECT * FROM Products " & _
        " JOIN Categories ON Products.CategoryID=Categories.CategoryID"
        Private _categoryName As String = "Beverages"
        Public Sub New()
            MyBase.Title = "Featured Product"
        End Sub
        <Personalizable(), WebBrowsable()> _
        Public Property CategoryName() As String
            Get
                Return _categoryName
            End Get
            Set(ByVal value As String)
                _categoryName = value
            End Set
        End Property
        Protected Overrides Sub RenderContents(ByVal writer _
          As HtmlTextWriter)
            ' Load Products into DataTable
            Dim productTable As New DataTable()
            Dim dad As New SqlDataAdapter(selectString, _
              connectionString)
            dad.Fill(productTable)
            ' Filter the DataTable with a Category
            Dim productView As DataView = productTable.DefaultView
            productView.RowFilter = "CategoryName='" & _
              _categoryName & "'"
            If productView.Count = 0 Then
                Return
            End If
            ' Randomly select a row
            Dim rnd As New Random()
            Dim row As DataRowView = productView(Rnd.Next(productView.Count))
            ' Render the row
            writer.Write(row("ProductName").ToString())
            writer.Write(String.Format("- {0:c}", row("UnitPrice")))
        End Sub
    End Class
End Namespace

The modified Featured Product control in Listing 3 includes a CategoryName property. Notice that this property has two attributes associated with it: the Personalizable and WebBrowsable attributes.

When a property is marked as Personalizable, the state of the property is automatically saved when the property is modified. When a property is marked as WebBrowsable, the property can be modified in a designer such as the PropertyGridEditorPart.

By default, the value of a property marked as Personalizable is saved to a database table. The value is automatically saved to a Microsoft SQL Server 2005 Express database located in your application root App_Data folder. You can modify the location where personalization data is saved by modifying your application's Web Configuration file.

In order for the right value to be associated with the right user, the ASP.NET Framework must be able to identify the current user. You can use personalization with both Windows and Forms Authentication (Windows Authentication is the default). When using Forms Authentication, the user must be authenticated before the user requests the page.

The modified Home page in Listing 4 enables you to customize the CategoryName property at runtime:

Listing 4. Home2.aspx (C#)

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    void CustomizePage(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = 
          WebPartManager.CatalogDisplayMode;
    }
    void EditWebParts(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
    }
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart2 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>  

Listing 4. Home2.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    Sub CustomizePage(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
    End Sub
    Sub EditWebParts(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode
    End Sub
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart2 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

The modified Home page in Listing 4 includes a new Edit Web Parts link. When you click this link, the EditWebParts method executes and sets the DisplayMode property to the value EditDisplayMode. When the page is set to EditDisplayMode, you can select a Web Part control's Edit menu option and edit its properties (see Figure 4).

ms379628.webparts_fig04(en-US,VS.80).gif

Figure 4. Editing a Web Part

The CategoryName property is displayed by the PropertyGridEditorPart control. This control is contained within the EditorZone control that appears in the third column (the rightmost column) of the Home page.

If you enter the value Seafood for the CategoryName property, then the Featured Product Web Part will randomly display only those products contained in the Seafood category. If you enter the value Beverages, then only those products contained in the Beverages category are displayed. Notice that you add more than one instance of the Featured Product Web Part to the page and assign different categories to each Web Part instance.

Creating a Custom Web Part Menu

In the previous section, you learned how to enable users to select a particular category of products to display by taking advantage of the PropertyGridEditorPart control. In order to select a category using this method, the user must selected Edit from the Web Part menu and update the CategoryName property.

In this section, you'll learn about a more elegant method of allowing users to modify the properties of a Web Part at runtime. In this section, you'll learn how to add custom verbs to a Web Part menu (see Figure 5).

ms379628.webparts_fig05(en-US,VS.80).gif

Figure 5. Using a Custom Web Part Menu

The modified Featured Product Web Part in Listing 5 overrides the WebPart control's Verbs property.

Listing 5. FeaturedProductPart3.cs (C#)

using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
    public class FeaturedProductPart3 : WebPart
    {
        const string connectionString =
        "Server=localhost;Trusted_Connection=True;Database=Northwind";
        const string selectString = "SELECT * FROM Products " +
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID";
        private string _categoryName = "Beverages";
        public FeaturedProductPart3()
        {
            this.Title = "Featured Product";
        }
        public override WebPartVerbCollection Verbs
        {
            get
            {
                // Create Beverages verb
                WebPartVerb verb1 = new WebPartVerb("verb1", new
                  WebPartEventHandler(ChangeCategory));
                verb1.Text = "Beverages";
                verb1.Description = "Displays Beverages Products";
                // Create Seafood verb
                WebPartVerb verb2 = new WebPartVerb("verb2", new
                  WebPartEventHandler(ChangeCategory));
                verb2.Text = "Seafood";
                verb2.Description = "Displays Seafood Products";
                // Create collection of verbs
                WebPartVerb[] verbs = new WebPartVerb[] { verb1, verb2 };
                // Return WebPartVerbCollection including base verbs
                return new WebPartVerbCollection(verbs);
            }
        }
        public void ChangeCategory(Object s, WebPartEventArgs e)
        {
            string newCategory = ((WebPartVerb)s).Text;
            this.CategoryName = newCategory;
            this.Title = String.Format("Featured Product - {0}",
             newCategory);
        }
        [Personalizable, WebBrowsable]
        public string CategoryName
        {
            get { return _categoryName; }
            set { _categoryName = value; }
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            // Load Products into DataTable
            DataTable productTable = new DataTable();
            SqlDataAdapter dad = new SqlDataAdapter(selectString,
              connectionString);
            dad.Fill(productTable);
            // Filter the DataTable with a Category
            DataView productView = productTable.DefaultView;
            productView.RowFilter = "CategoryName='" +
              _categoryName + "'";
            if (productView.Count == 0)
                return;
            // Randomly select a row
            Random rnd = new Random();
            DataRowView row = productView[rnd.Next(productView.Count)];
            // Render the row
            writer.Write((string)row["ProductName"]);
            writer.Write(String.Format("- {0:c}", row["UnitPrice"]));
        }
    }
}

Listing 5. FeaturedProductPart3.vb (Visual Basic .NET)

Imports System.Collections
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class FeaturedProductPart3
        Inherits WebPart
        Const connectionString As String = _
          "Server=localhost;Trusted_Connection=True;Database=Northwind"
        Const selectString As String = "SELECT * FROM Products " & _
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID"
        Private _categoryName As String = "Beverages"
        Public Sub New()
            MyBase.Title = "Featured Product"
        End Sub
        Public Overrides ReadOnly Property Verbs() As _
          WebPartVerbCollection
            Get
                ' Create Beverages verb
                Dim verb1 As New WebPartVerb("verb1", New _
                  WebPartEventHandler(AddressOf ChangeCategory))
                verb1.Text = "Beverages"
                verb1.Description = "Displays Beverages Products"
                ' Create Seafood verb
                Dim verb2 As New WebPartVerb("verb2", New _
                  WebPartEventHandler(AddressOf ChangeCategory))
                verb2.Text = "Seafood"
                verb2.Description = "Displays Seafood Products"
                ' Create collection of verbs
                Dim newVerbs() As WebPartVerb = {verb1, verb2}
                ' Return WebPartVerbCollection including base verbs
                Return New WebPartVerbCollection(newVerbs)
            End Get
        End Property
        Public Sub ChangeCategory(ByVal s As Object, _
          ByVal e As WebPartEventArgs)
            Dim newCategory As String = s.Text
            Me.CategoryName = newCategory
            Me.Title = String.Format("Featured Product - {0}", _
              newCategory)
        End Sub
        <Personalizable(), WebBrowsable()> _
        Public Property CategoryName() As String
            Get
                Return _categoryName
            End Get
            Set(ByVal value As String)
                _categoryName = value
            End Set
        End Property
        Protected Overrides Sub RenderContents(ByVal writer _
          As HtmlTextWriter)
            ' Load Products into DataTable
            Dim productTable As New DataTable()
            Dim dad As New SqlDataAdapter(selectString, _
              connectionString)
            dad.Fill(productTable)
            ' Filter the DataTable with a Category
            Dim productView As DataView = productTable.DefaultView
            productView.RowFilter = "CategoryName='" & _
              _categoryName & "'"
            If productView.Count = 0 Then
                Return
            End If
            ' Randomly select a row
            Dim rnd As New Random()
            Dim row As DataRowView = _
              productView(Rnd.Next(productView.Count))
            ' Render the row
            writer.Write(row("ProductName").ToString())
            writer.Write(String.Format("- {0:c}", row("UnitPrice")))
        End Sub
    End Class
End Namespace

In Listing 5, the Verbs property is overridden and two custom verbs are created. Both verbs are associated with a Web Part event handler named ChangeCategory. This event handler is executed when a user selects either of the two menu options.

The ChangeCategory method retrieves the text of the selected menu verb. Next, the method changes the value of the CategoryName property and the title displayed by the Web Part.

You can use the modified Featured Product Web Part with the page in Listing 6:

Listing 6. Home3.aspx (C#)

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    void CustomizePage(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = 
          WebPartManager.CatalogDisplayMode;
    }
    void EditWebParts(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
    }
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart3 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html

Listing 6. Home3.aspx (Visual Basic .NET)

<%@ Page Language="vb" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    Sub CustomizePage(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
    End Sub
    Sub EditWebParts(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode
    End Sub
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:PageCatalogPart 
                    ID="PageCatalogPart1"
                    Runat="server" />
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart3 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

Notice that you can select the custom menu options even when the display mode for the page is not set to either CatalogDisplayMode or EditDisplayMode. Users can modify the product category at any time.

By default, custom verbs appear in a drop-down menu. If you prefer, you can alternatively display all the menu items statically in a Web Part's title bar. You can change the appearance of menu verbs by setting the WebPartVerbRenderMode property of any Web Part Zone.

Communicating Information Between Web Parts

You can share information between different Web Parts placed on a page by creating connections between the Web Parts. In this section, we'll create two new Web Parts. The first Web Part, named SelectCategoryPart, enables a user to select a product category from a dropdown list. The second Web Part, named FeaturedProductPart4, randomly displays a product from the selected category (see Figure 6).

ms379628.webparts_fig06(en-US,VS.80).gif

Figure 6. Using Connected Web Parts

There are two ways in which you can connect Web Parts in a page: You can create either a static connection or a dynamic connection. A static connection enables you to declaratively specify the connections in a page. A dynamic connection, on the other hand, can be created by a user at runtime. In this section, we will create a static connection between the SelectCategoryPart and the FeaturedProductPart4 Web Parts.

There are four steps that you must complete to create a static connection between two Web Parts:

  1. You must define an interface specifying the methods and properties that are shared between the connected Web Parts.
  2. You must add the ConnectionProvider attribute to the Web Part used to provide the shared information.
  3. You must add the ConnectionConsumer attribute to the Web Part used to consume the shared information.
  4. You must declare the connections within the StaticConnections sub tag of the WebPartManager class.

Let's start with the first step. The interface declared in Listing 7—named ICategoryInterface—contains a single property named SelectedCategory:

Listing 7. ICategoryInterface.cs (C#)

namespace myControls
{
    public interface ICategoryInterface
    {
        string SelectedCategory { get; }
    }
}

Listing 7. ICategoryInterface.vb (Visual Basic .NET)

Namespace myControls
    Public Interface ICategoryInterface
        ReadOnly Property SelectedCategory() As String
    End Interface
End Namespace

We'll use the ICategoryInterface with our provider Web Part. Our provider Web Part, named SelectCategoryPart, is contained in Listing 8:

Listing 8. SelectCategoryPart.cs (C#)

using System;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
 
    public class SelectCategoryPart : WebPart, ICategoryInterface
    {
        DropDownList dropCategories;
        public SelectCategoryPart()
        {
            this.Title = "Product Category";
        }
        public string SelectedCategory
        {
            get
            {
                EnsureChildControls();
                return dropCategories.SelectedValue;
            }
        }
        [ConnectionProvider("Category")]
        public ICategoryInterface ProvideInterface()
        {
            return this;
        }
        protected override void CreateChildControls()
        {
            dropCategories = new DropDownList();
            dropCategories.AutoPostBack = true;
            dropCategories.Items.Add("Beverages");
            dropCategories.Items.Add("Seafood");
            Controls.Add(dropCategories);
        }
    }
}

Listing 8. SelectCategoryPart.vb (Visual Basic .NET)

Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class SelectCategoryPart
        Inherits WebPart
        Implements ICategoryInterface
        Dim dropCategories As DropDownList
        Public Sub New()
            MyBase.Title = "Product Category"
        End Sub
        Public ReadOnly Property SelectedCategory() As String _
          Implements ICategoryInterface.SelectedCategory
            Get
                EnsureChildControls()
                Return dropCategories.SelectedValue
            End Get
        End Property
        <ConnectionProvider("Category")> _
        Public Function ProvideInterface() As ICategoryInterface
            Return Me
        End Function
        Protected Overrides Sub CreateChildControls()
            dropCategories = New DropDownList()
            dropCategories.AutoPostBack = True
            dropCategories.Items.Add("Beverages")
            dropCategories.Items.Add("Seafood")
            Controls.Add(dropCategories)
        End Sub
    End Class
End Namespace

The SelectCategoryPart contained in Listing 8 displays a dropdown list that contains two values: Beverages and Seafood. Selecting a value in the dropdown list modifies the value of the SelectedCategory property.

Notice that the SelectCategoryPart control includes a method named ProvideInterface. This method is marked with an attribute named ConnectionProvider. The ConnectionProvider attribute identifies the method that returns the connection interface (the ICategoryInterface interface).

Notice that the SelectCategoryPart control itself implements the ICategoryInterface interface. Since the SelectCategoryPart implements the ICategoryInterface, the ProvideInterface method can simply return an instance of the current class.

The modified Featured Product Web Part in Listing 8 uses the SelectCategoryPart to determine which category of products to display:

Listing 9. FeaturedProductPart4.cs (C#)

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
    public class FeaturedProductPart4 : WebPart
    {
        const string connectionString = 
        "Server=localhost;Trusted_Connection=True;Database=Northwind";
        const string selectString = "SELECT * FROM Products " +
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID";
        private string _categoryName = "Beverages";
        private ICategoryInterface _category;
        public FeaturedProductPart4()
        {
            this.Title = "Featured Product";
        }
        [Personalizable, WebBrowsable]
        public string CategoryName
        {
            get { return _categoryName; }
            set { _categoryName = value; }
        }
        [ConnectionConsumer("Category")]
        public void GetInterface(ICategoryInterface myInterface)
        {
            _category = myInterface;
        }
        
        protected override void OnPreRender(EventArgs e)
        {
            if (_category != null)
            {
                CategoryName = _category.SelectedCategory;
                this.Title = String.Format("Featured Product - {0}", 
                  CategoryName);
            }
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            // Load Products into DataTable
            DataTable productTable = new DataTable();
            SqlDataAdapter dad = new SqlDataAdapter(selectString, 
              connectionString);
            dad.Fill(productTable);
            // Filter the DataTable with a Category
            DataView productView = productTable.DefaultView;
            productView.RowFilter = "CategoryName='" + 
              _categoryName + "'";
            if (productView.Count == 0)
                return;
            // Randomly select a row
            Random rnd = new Random();
            DataRowView row = productView[rnd.Next(productView.Count)];
            // Render the row
            writer.Write((string)row["ProductName"]);
            writer.Write(String.Format("- {0:c}", row["UnitPrice"]));
        }
    }
}

Listing 9. FeaturedProductPart4.vb (Visual Basic .NET)

Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class FeaturedProductPart4
        Inherits WebPart
        Const connectionString As String = _
          "Server=localhost;Trusted_Connection=True;Database=Northwind"
        Const selectString As String = "SELECT * FROM Products " & _
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID"
        Private _categoryName As String = "Beverages"
        Private _category As ICategoryInterface
        Public Sub New()
            MyBase.Title = "Featured Product"
        End Sub
        <Personalizable(), WebBrowsable()> _
        Public Property CategoryName() As String
            Get
                Return _categoryName
            End Get
            Set(ByVal value As String)
                _categoryName = value
            End Set
        End Property
        <ConnectionConsumer("Category")> _
        Public Sub GetInterface(ByVal myInterface As _
          ICategoryInterface)
            _category = myInterface
        End Sub
        Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
            If Not IsNothing(_category) Then
                CategoryName = _category.SelectedCategory
                MyBase.Title = String.Format("Featured Product - {0}", _
                CategoryName)
            End If
        End Sub
        Protected Overrides Sub RenderContents(ByVal writer As _
          HtmlTextWriter)
            ' Load Products into DataTable
            Dim productTable As New DataTable()
            Dim dad As New SqlDataAdapter(selectString, _
              connectionString)
            dad.Fill(productTable)
            ' Filter the DataTable with a Category
            Dim productView As DataView = productTable.DefaultView
            productView.RowFilter = "CategoryName='" & CategoryName & "'"
            If productView.Count = 0 Then
                Return
            End If
            ' Randomly select a row
            Dim rnd As New Random()
            Dim row As DataRowView = _
              productView(Rnd.Next(productView.Count))
            ' Render the row
            writer.Write(row("ProductName").ToString())
            writer.Write(String.Format("- {0:c}", row("UnitPrice")))
        End Sub
    End Class
End Namespace 

The modified Featured Product Web Part in Listing 9 contains a new method named GetInterface. When the Featured Product Web Part is connected to the SelectCategoryPart control, the SelectCategoryPart is passed to this method. The value passed to the method is assigned to a local field named _category.

The _category field is used within the OnPreRender method. The selected category is assigned to the CategoryName property and the title of the Web control is updated.

The final step concerns the page that is used to display the Web Parts. The modified Home page in Listing 10 declares a static connection between the SelectCategoryPart Web Part and the FeaturedProductPart4 Web Part:

Listing 10. Home4.aspx

<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server">
        <StaticConnections>
     <asp:WebPartConnection
                id="Connection1" 
                ProviderID="SelectCategoryPart1" 
                ConsumerID="myFeaturedProduct" />
        </StaticConnections>
    </asp:WebPartManager>
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server">
            <ZoneTemplate>
                  <custom:SelectCategoryPart
                    ID="SelectCategoryPart1" 
                    Runat="Server" />
            </ZoneTemplate>
        </asp:WebPartZone>
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            Runat="Server">
            <ZoneTemplate>
                  <custom:FeaturedProductPart4 
                    ID="myFeaturedProduct"
                    Runat="Server" />
            </ZoneTemplate>
        </asp:WebPartZone>
        </td>
    </tr>    
    </table>
    </form>
</body>
</html

Notice that the WebPartManager control contains a StaticConnections section. This section is used to connect the SelectCategoryPart with the FeaturedProductPart4 Web Part.

Importing and Exporting Web Part Settings

In this final section, we'll examine one last advanced feature of Web Parts: how you can import and export Web Part Settings.

Importing and Exporting Web Parts is useful when you want to share settings between different Web applications or even different Web sites. If more than one Web site is using exactly the same Web Part (It doesn't have to be the same assembly, but the Web Part must have the same properties), you can apply your settings across the multiple Web sites.

The modified Featured Product Web Part in Listing 11 is configured to allow users to export its settings.

Listing 11. FeaturedProductPart5.cs (C#)

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myControls
{
    public class FeaturedProductPart5 : WebPart
    {
        const string connectionString = 
        "Server=localhost;Trusted_Connection=True;Database=Northwind";
        const string selectString = "SELECT * FROM Products " + 
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID";
        private string _categoryName = "Beverages";
        
        public FeaturedProductPart5()
        {
            this.Title = "Featured Product";
            this.ExportMode = WebPartExportMode.All;
        }
        [Personalizable, WebBrowsable]
        public string CategoryName
        {
            get { return _categoryName; }
            set { _categoryName = value; }
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            // Load Products into DataTable
            DataTable productTable = new DataTable();
            SqlDataAdapter dad = new SqlDataAdapter(selectString, 
              connectionString);
            dad.Fill(productTable);
            // Filter the DataTable with a Category
            DataView productView = productTable.DefaultView;
            productView.RowFilter = "CategoryName='" + 
              _categoryName + "'";
            if (productView.Count == 0)
                return;
            // Randomly select a row
            Random rnd = new Random();
            DataRowView row = productView[rnd.Next(productView.Count)];
            // Render the row
            writer.Write((string)row["ProductName"]);
            writer.Write(String.Format("- {0:c}", row["UnitPrice"]));
        }
    }
}

Listing 11. FeaturedProductPart5.vb (Visual Basic .NET)

Imports System.Collections
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts
Namespace myControls
    Public Class FeaturedProductPart5
        Inherits WebPart
        Const connectionString As String = _
        "Server=localhost;Trusted_Connection=True;Database=Northwind"
        Const selectString As String = "SELECT * FROM Products " & _
        "JOIN Categories ON Products.CategoryID=Categories.CategoryID"
        Private _categoryName As String = "Beverages"
        Public Sub New()
            MyBase.Title = "Featured Product"
            MyBase.ExportMode = WebPartExportMode.All
        End Sub
        <Personalizable(), WebBrowsable()> _
        Public Property CategoryName() As String
            Get
                Return _categoryName
            End Get
            Set(ByVal value As String)
                _categoryName = value
            End Set
        End Property
        Protected Overrides Sub RenderContents(ByVal writer As _
          HtmlTextWriter)
            ' Load Products into DataTable
            Dim productTable As New DataTable()
            Dim dad As New SqlDataAdapter(selectString, _
              connectionString)
            dad.Fill(productTable)
            ' Filter the DataTable with a Category
            Dim productView As DataView = productTable.DefaultView
            productView.RowFilter = "CategoryName='" & 
              _categoryName & "'"
            If productView.Count = 0 Then
                Return
            End If
            ' Randomly select a row
            Dim rnd As New Random()
            Dim row As DataRowView = _
              productView(rnd.Next(productView.Count))
            ' Render the row
            writer.Write(row("ProductName").ToString())
            writer.Write(String.Format("- {0:c}", row("UnitPrice")))
        End Sub
    End Class
End Namespace

Take a look at the constructor for the Web Part in Listing 11. The constructor sets the ExportMode for the Web Part to the value WebPartExportMode.All. Setting this property enables exporting for the Web Part.

There is one other step that you must complete to enable Web Parts to be exported. You must add the enableExport setting to your application's configuration file. The Web.Config file in Listing 12 contains the proper setting.

Listing 12. Web.Config

<?xml version="1.0"?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.web>
        <webParts enableExport="true" />
    </system.web>
</configuration>

The modified Home page in Listing 13 can be used to host the exportable Web Part.

Listing 13. Home5.aspx (C#)

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    void CustomizePage(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = 
          WebPartManager.CatalogDisplayMode;
    }
    void EditWebParts(Object s, EventArgs e)
    {
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
    }
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart5 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
                <asp:ImportCatalogPart 
                    ID="ImportCatalogPart1" 
                    Runat="server" />
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

Listing 13. Home5.aspx (Visual Basic .NET)

<%@ Page Language="vb" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<script runat="server">
    
    Sub CustomizePage(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
    End Sub
    Sub EditWebParts(ByVal s As Object, ByVal e As EventArgs)
        WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode
    End Sub
            
</script>
<html>
<head id="Head1" runat="server">
    <title>Home Page</title>
</head>
<body bgcolor="gray">
    <form id="form1" runat="server">
    <asp:WebPartManager
        ID="WebPartManager1" 
        Runat="Server" />
    
    <table width="100%" Height="100%" cellpadding="5" cellspacing="10">
    <tr height="50">
        <td colspan="3" align="right" bgcolor="white">
        <asp:LinkButton ID="LinkButton1" 
            Text="Customize Page" 
            OnClick="CustomizePage"
            Runat="Server" />
        |
        <asp:LinkButton ID="LinkButton2" 
            Text="Edit Web Parts" 
            OnClick="EditWebParts"
            Runat="Server" />
        </td>
    </tr>
    <tr>
        <td valign="top" bgcolor="white" width="40%">
        <asp:WebPartZone 
            ID="WebPartZone1"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" bgcolor="white" width="40%">    
        <asp:WebPartZone
            ID="WebPartZone2"
            Width="100%"
            HeaderStyle-BackColor="lightblue"
            PartTitleStyle-BackColor="silver"
            MenuStyle-BackColor="#eeeeee"
            MenuStyle-BorderStyle="solid"
            MenuStyle-BorderWidth="1"
            MenuStyle-BorderColor="black"
            Runat="Server" />
        </td>
        <td valign="top" width="20%" bgcolor="white">
            <asp:CatalogZone 
                ID="CatalogZone1"  
                Runat="server">
            <ZoneTemplate>
                <asp:DeclarativeCatalogPart 
                    ID="DeclarativeCatalogPart1" 
                    Runat="server">
                  <WebPartsTemplate>
                    <custom:FeaturedProductPart5 
                        ID="myFeaturedProduct"
                        Runat="Server" />
                  </WebPartsTemplate>
                </asp:DeclarativeCatalogPart>
                <asp:ImportCatalogPart 
                    ID="ImportCatalogPart1" 
                    Runat="server" />
            </ZoneTemplate>
            </asp:CatalogZone>
            
            <asp:EditorZone
                ID="EditorZone1"
                Runat="Server">
                <ZoneTemplate>
                <asp:PropertyGridEditorPart
                    id="PropertyGridEditorPart1"
                    Runat="Server" />
                </ZoneTemplate>
            </asp:EditorZone>
 
        </td>
    </tr>    
    </table>
    </form>
</body>
</html>

When you open the page in Listing 14, you can click the Customize Page link and add one or more Featured Product Web Parts to the Web Zones in the page. After you add a Web Part, you can export its settings by selecting Export from the Web Part's menu (see Figure 7).

ms379628.webparts_fig07(en-US,VS.80).gif

Figure 7. Exporting a Web Part

When you export a Web Part, a file named FeaturedProduct.WebPart is created, which you can save to your local hard drive. The contents of this file are contained in Listing 15. Notice that it is an XML file that simply lists the values of the Web Part's properties, including the CategoryName property.

Listing 14. FeaturedProduct.WebPart

<?xml version="1.0" encoding="utf-8"?>
<webParts>
  <webPart>
    <metaData>
      <type name="myControls.FeaturedProductPart5" />
      <importErrorMessage>Cannot import this Web 
       Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="TitleIconImageUrl" />
        <property name="AllowClose">True</property>
        <property name="ChromeState">Normal</property>
        <property name="CategoryName">Beverages</property>
        <property name="TitleUrl" />
        <property name="AllowZoneChange">True</property>
        <property name="Hidden">False</property>
        <property name="Direction">NotSet</property>
        <property name="HelpMode">Navigate</property>
        <property name="ChromeType">Default</property>
        <property name="CatalogIconImageUrl" />
        <property name="Height" />
        <property name="Description" />
        <property name="AllowHide">True</property>
        <property name="AllowMinimize">True</property>
        <property name="AllowEdit">True</property>
        <property name="Title">Featured Product</property>
        <property name="Width" />
        <property name="ExportMode">All</property>
        <property name="HelpUrl" />
      </properties>
    </data>
  </webPart>
</webParts>

After you export a Web Part, you can import the Web Part by clicking the Customize Page Link. One of the catalogs displayed is called the Imported Web Part Catalog. If you select this catalog, you can upload the Web Part that you previously exported (see Figure 8).

ms379628.webparts_fig08(en-US,VS.80).gif

Figure 8. Importing a Web Part

Conclusion

Web Parts provide you with an entire programming framework that rests on top of the ASP.NET 2.0 Framework. This new framework has powerful features, enabling you to build Web applications that can be customized by Web site users and administrators at runtime.

Unlike other features of the ASP.NET 2.0 Framework, you won't need Web Parts for every Web application that you will build. However, if you plan to create customizable portals or Web sites, Web Parts will be invaluable.

 

About the author

Stephen Walther wrote the best-selling book on ASP.NET, ASP.NET Unleashed. He was also the architect and lead developer of the ASP.NET Community Starter Kit, a sample ASP.NET application produced by Microsoft. He has provided ASP.NET training to companies across the United States, including NASA and Microsoft, through his company Superexpert.

© Microsoft Corporation. All rights reserved.