- ASP.NET Profiles
- Web Parts Framework
- Summary
- Exercises
- Key Concepts
- References
Web Parts Framework
In the last section, we examined how you can use the profile system in ASP.NET to programmatically customize Web content for specific users. In this section, we examine the Web parts framework that was introduced in ASP.NET 2.0. The Web parts framework is the name for the infrastructure and controls that allow you to create pages that provide end users with the ability to modify the content, appearance, and behavior of these pages directly within the browser. When users modify pages and controls, the settings are saved so as to retain the user's personal preferences across future browser sessions. Thus, the Web parts framework lets you let develop sites that can be customized by users at runtime without developer or administrator intervention. Figure 14.6 illustrates a sample page that is using Web parts. Each of these rectangular boxes of content can be moved into different locations on the page by the user, as well as minimized, restored, and closed.
Figure 14.6 Sample page using Web parts
This type of personalized Web site is sometimes called a portal. A portal is a type of Web application that displays different types of information, usually based on some type of prior user setup. A portal site may provide a unified interface to an organization's information or it may aggregate information from multiple organizations. Microsoft's My MSN and Google's Personalized Home Page are examples of very general-purpose portals.
Microsoft has a dedicated product named SharePoint 2003 that can be used to create portal sites for managing collaboration between geographically dispersed team members. A SharePoint portal is constructed using something called SharePoint Web parts, which (in the current version) are extensions of ASP.NET 1.1 controls that can interact with the SharePoint framework. The rest of this chapter covers the ASP.NET 2.0 Web parts framework, which is different than the SharePoint Web parts framework but has a similar functionality. Finally, to muddy the waters further, with SharePoint Service Pack 2, SharePoint Services can now use ASP.NET 2.0 Web parts; as well, future versions of SharePoint are to use ASP.NET 2.0 Web parts as their default content model.
Web Parts, Web Part Zones, and the Web Part Manager
The ASP.NET Web parts framework allows you to create user-customizable pages. These pages consist of regular ASP.NET content as well as special content containers called Web parts. A Web part is a type of Web server container control; it is a special container for both ASP.NET and regular HTML content. A Web part is thus a user-customizable "box" of displayable content that is provided with additional user interface elements such as chrome and verbs. Figure 14.7 illustrates many of the additional elements that are added to a Web part.
Figure 14.7 Components of a Web part
The verbs of a Web part are the actions that a user can instruct the Web part to perform. By default, the Web part framework adds the minimize/restore and close verbs to each Web part. Depending upon the display mode of the Web parts, other verbs may be available, such as edit, connect, and export. As well, it is possible to define custom verbs for a Web part.
The chrome of a Web part refers to the common user interface elements rendered around each Web part. The chrome for a Web part includes a border, a title bar, and the icons, title text, and verb menu that appear within the title bar.
Web parts are placed into one or more Web part zones, which is yet another specialized container control (named WebPartZone). It is the Web part zone that defines the chrome and verbs for the Web parts that it contains. Figure 14.8 illustrates the relationship between parts and zones.
Figure 14.8 Web parts and Web part zones
Finally, there is the Web part manager, which manages the entire Web part infrastructure on a page. This manager is a nonrendered control named WebPartManager. This control must be present on any page that uses the Web part infrastructure. The manager control can also be used to programmatically change the display mode of the Web parts or to set up communication between Web parts.
Figure 14.9 illustrates some (but not all) of the different display modes available to the Web part manager. These different display modes may also require the use of different types of zones: for instance, the CatalogZone, EditorZone, and ConnectionsZone.
Figure 14.9 Web part manager display modes
Internally, a Web part is a server control that ultimately inherits from the WebPart abstract base class. As you can see from Figure 14.10, Web parts are specializations of the Panel server control. As can also be seen from this diagram, Web parts are one of three different part types in the Web parts framework. The editor part allows users to modify the appearance of a Web part, whereas the catalog part provides a catalog or list of Web parts that a user can add to a page.
Figure 14.10 Web parts class hierarchy
Web Parts Personalization
Another feature of the Web parts infrastructure is its support for personalization. Web parts personalization refers to the capability of the current state of Web part property values to be identified with users and saved in long-term storage. It should be stressed that Web parts personalization stores user-specific state data for controls on a particular Web page only. Information that relates to the user as a person or that is intended to be used across multiple pages in a Web application should be kept in a profile.
The Web parts personalization system is based on the provider model, and by default, uses the same database as the membership and profile systems. Just as with the other provider-based subsystems in ASP.NET, it is possible to create your own customized personalization provider so as to use any data store.
Web parts personalization requires authenticated users. Although Web parts are visible for anonymous users, the personalization of Web parts requires the user to be logged in first. Normally, any personalization changes that the user makes to the Web parts in a page are saved just for that user, so that the next time the user visits the page, the previous changes are visible. In Web parts terminology, this is called user scope.
The other possible scope is shared scope. With shared scope, any changes that the user makes are visible to all users. By default, all users (authorized or anonymous) are denied access to shared scope. It might make sense, however, for site administrators to have shared scope so that they can make changes that are visible to all users. We illustrate how to set up shared scope later in the chapter in the section "Making the BehaviorEditorPart Appear" on page 884.
Creating and Using Web Parts
There are three basic approaches to creating a Web part:
- Use any Web server control or HTML server control within a WebPartZone control. When you add a server control to a WebPartZone, the runtime wraps the control as a GenericWebPart object, and thus is treated as a WebPart control and has the same consistent Web part user interface elements such as verbs, icons, a title, and a header.
- Create a user control that implements the IWebPart interface.
- Create a custom control that derives from the WebPart abstract class.
In this chapter, we begin with the first approach and then move onto the second approach, because it provides us with more control and cleaner markup. We then finish with the third approach, because it provides the highest degree of customization and control.
Because we begin by letting ASP.NET convert normal server controls into Web parts, let us examine the steps necessary for creating a page with Web parts.
- Add a WebPartManager control to the page, as shown in the following.
<asp:WebPartManager ID="partManager" runat="server" />
Add one or more WebPartZone controls to the page. This control is the container that will hold the Web parts. Each WebPartZone control contains a ZoneTemplate, which contains the Web parts. As well, each WebPartZone control can contain various style elements, such as HeaderStyle and PartChromeStyle, which allow you to define and customize the appearance of the Web parts within the zone.
<asp:WebPartZone ID="wpzOne" runat="server" > <ZoneTemplate> ... </ZoneTemplate> style elements can go here <PartChromeStyle ... /> ... </asp:WebPartZone>
Most pages have at least two different zones, which correspond broadly to the different layout areas for the page. For instance, in Figure 14.7, the page structure contains two columns. Each of these columns contains a separate WebPartZone control. Some developers place different zones into different columns of an HTML <table> element; other developers place the different zones into different HTML <div> elements and then use CSS to position the zones. The latter approach is used in this chapter.
- Add one or more Web parts to the different zones. Recall that any ASP.NET control, whether a standard control—such as a GridView or a Label control, a user control, or even a custom control—can be used as a Web part. In the following example, two Web parts (a Calendar control and a LoginView control) are added to a Web part zone.
<asp:WebPartZone ID="wpzOne" runat="server" > <ZoneTemplate> <asp:Calendar ID="calOne" runat="server" /> <asp:LoginView ID="logView" runat="server"> <AnonymousTemplate> <asp:Login ID="logSignin" runat="server" /> </AnonymousTemplate> <LoggedInTemplate> <asp:LoginName ID="logName" runat="server" FormatString="Welcome {0}" /> </LoggedInTemplate> </asp:LoginView> </ZoneTemplate> </asp:WebPartZone>
- Optionally add specialized zones and parts, such as the CatalogZone or the EditorZone.
The result in the browser is shown in Figure 14.11. Notice how the chrome for the Web parts changes after the user is authenticated. Anonymous users still see the Web parts; however, they do not see the verb menu, nor are they able to view the page in anything other than the default browse display mode.
Figure 14.11 Simple Web part example
Of course, the example Web parts shown in Figure 14.11 are quite plain. You can style the Web parts by adding style elements to the WebPartZone (and by setting properties of the WebPartZone control). As well, within the Visual Studio designer, you can use the AutoFormat option to apply a prebuilt set of formatting options to a zone. Table 14.3 lists the different style elements available to the WebPartZone, whereas Table 14.4 lists the different properties of the WebPartZone control.
Table 14.3. WebPartZone Style Elements
Name |
Description |
EmptyZoneTextStyle |
The style for the text that appears in an empty zone. |
ErrorStyle |
The style for any error messages that are displayed if Web part cannot be loaded or created. |
FooterStyle |
The style of the text that appears in the footer of the zone. |
HeaderStyle |
The style of the text that appears in the header of the zone. |
MenuCheckImageStyle |
The style of the check mark that may appear beside a verb in the verb menu. |
MenuLabelHoverStyle |
The style of the text label for the verb menu when user positions the mouse pointer over the label. |
MenuLabelStyle |
The style of the text label for the verb menu. |
MenuPopupStyle |
The style of the pop-up verb menu for each Web part in the zone. |
MenuVerbHoverStyle |
The style of the verb menu items when user positions the mouse pointer over the text. |
MenuVerbStyle |
The style of the verb menu items. |
PartChromeStyle |
The style of the borders for each Web part in the zone. |
PartStyle |
The style of the title bar for each Web part in the zone. |
PartTitleStyle |
The style of the title in the title bar for each Web part in the zone. |
SelectedPartChromeStyle |
The style of the chrome for the selected Web part in a zone. A Web part is selected when it is in edit mode. |
TitleBarVerbStyle |
The style of the verbs in the title bar. |
VerbStyle |
The style for the verbs for each Web part in the zone. |
Table 14.4. Notable WebPartZone Properties
Property |
Description |
AllowLayoutChange |
Indicates whether the user is allowed to change the layout of Web parts within the zone. Default is true. |
BackImageUrl |
The URL of an image to display in the background of a zone. |
DragHighlightColor |
The color to be displayed around the border of a Web zone and its drop-cue when a user is dragging a Web part. |
EmptyZoneText |
The text to be displayed when a Web zone contains no Web parts. |
HeaderText |
The text to display in the header of the zone. |
LayoutOrientation |
Indicates whether the controls in a zone are to be arranged horizontally or vertically. |
MenuCheckImageUrl |
The URL of image to display when a verb is checked. |
MenuLabelText |
The text label for the pop-up verb menu in each Web part. |
MenuPopupImageUrl |
The URL for image that opens the drop-down menu in the title bar of each Web part. |
Padding |
The cell padding for the table that contains the Web parts in the zone. |
ShowTitleIcons |
Indicates whether title icons are to be displayed in the title bar of each Web part in the zone. |
TitleBarVerbButtonType |
Indicates what type of button to use (Button, Image, or Link) for the verbs in the title bar. |
VerbButtonType |
The type of button to use for the zone when rendered in an older browser. |
WebPartVerbRenderMode |
Specifies how to render verbs on the Web parts in the zone. Possible values are WebPartVerbRenderMode.Menu (verbs appear in a drop-down menu) and WebPartVerbRenderMode.TitleBar (verbs appear as links in title bar). |
All Web parts within a zone inherit the visual appearance defined by the zone. Thus, if you move the Web part from one zone to another, its appearance may change if the styling of the zone is different.
Verbs
Verbs are an important part of the Web part framework. They are the actions that a user can instruct a Web part to perform. When displayed with the default Browse display mode, a Web part has two verbs: Minimize/Restore and Close. When viewing a page anonymously, there is no user interface to represent these verbs. After a user is authenticated, however, the different verbs are available as either a pop-up menu or a series of text or image links in the title bar of each Web part, depending upon the value of the zone's WebPartVerbRenderMode property and of its verb setting elements, as shown in Figure 14.12.
Figure 14.12 Sample verbs in a Web part
As shown in Figure 14.12, each zone has its own verb setting element. Table 14.5 lists the verbs that are provided by the Web part framework. You can also create your own verbs; we illustrate how to do so later when we create a custom Web part control.
Table 14.5. Web Part Verbs
Name |
Description |
Close |
Closes the Web part. After a Web part is closed, the only way to bring it back is via the catalog. |
Connect |
Allows a connection to be made to another Web part. |
Delete |
Deletes a control permanently from a page. This verb is not allowed for Web parts added declaratively. |
Edit |
Allows the user to edit the Web part. When editing, the user can personalize the appearance, properties, layout, and content of the Web part. |
Export |
Allows the user to export an XML definition file for each Web part in a zone. |
Help |
Displays help for the Web part in a modal or modeless window, or by navigating to a help page. |
Minimize |
Minimizes the Web part. When minimized, only the title bar of the part is visible. |
Restore |
Restores a Web part from the minimized state. |
You can customize the settings for each verb in the zone via the verb setting elements, as shown in the following.
<asp:WebPartZone ... > ... <CloseVerb Enabled="false" /> <MinimizeVerb Text="Min" Description="Shrinks this box"/> <RestoreVerb Text="Res" Description="Restores this box"/> </asp:WebPartZone>
Each verb setting element supports the properties listed in Table 14.6.
Table 14.6. Notable Verb Properties
Property |
Description |
Checked |
Indicates whether custom state for the verb is currently active. If it is selected, a check box appears next to the verb. This property is not used with the system-provided verbs; it is only available for custom verbs. |
Description |
A short description of the verb that is displayed in the tool tip for the verb. |
Enabled |
Indicates whether the verb is enabled. |
ImageUrl |
The URL of the image to display for the verb. |
Text |
The text label for the verb. |
Visible |
Indicates whether the verb is visible. |
Making Web Parts from User Controls
You may have noticed from the example shown in Figure 14.11 that the title for each Web part begins with "Untitled." Why is this? You may remember that when you add a regular Web server control to a Web part zone, it is wrapped as a GenericWebPart control so that the control can exhibit the same functionality as true WebPart controls. As you can see from Figure 14.10, this control inherits from the WebPart base control, which in turn implements the IWebPart interface. This interface, which is described in Table 14.7, defines the minimal user interface properties needed by any Web part. If you want to change the default implementations of any of these IWebPart properties in your Web parts, you need to either create your own custom control for the Web part or create a user control for the Web part that implements the IWebPart interface.
Table 14.7. IWebPart Properties
Property |
Description |
CatalogIconImageUrl |
The URL of the image used to represent the Web part in the catalog. |
Description |
The summary description of the Web part for use in tool tips and the catalog. |
SubTitle |
The string that is concatenated with the Title property and displayed in the title bar of the Web part. |
Title |
The title of the Web part, which is displayed in the Web part's title bar. |
TitleIconImageUrl |
The URL of the image that is displayed in the Web part's title bar. |
TitleUrl |
The URL of a page that contains more information about the Web part. When set, the title in the title bar becomes a hyperlink. |
As you may recall from Chapter 6, a user control encapsulates the appearance and behavior of a part of a page's user interface. User controls have an extension of .ascx and their code-behind classes inherit from the UserControl rather than the Page class.
If you want to be able to display a different title as well as a title bar image—for example, the LoginView Web part shown earlier—you could define the following user control.
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="LoginControl.ascx.cs" Inherits="LoginControl" %> <asp:LoginView ID="logView" runat="server"> <AnonymousTemplate> <asp:Login ID="logSignin" runat="server" DisplayRememberMe="True" TitleText="" TextLayout="TextOnTop" RememberMeText="Remember me"> </asp:Login> </AnonymousTemplate> <LoggedInTemplate> <asp:LoginName ID="logName" runat="server" FormatString="Welcome {0}" /><br/> <asp:LoginStatus ID="logStatus" runat="server" /> </LoggedInTemplate> </asp:LoginView>
The code-behind for this user control might look like the following. Notice how the user control class definition must also implement the IWebPart interface. The implementation of this interface simply consists of properties as well as the data members to contain the state for these properties.
public partial class LoginControl : UserControl, IWebPart { // Data members necessary for IWebPart properties private string _description = ""; private string _title = ""; private string _subtitle = ""; private string _titleIconImageUrl = ""; private string _catalogIconImageUrl = ""; private string _titleUrl = ""; // Implement IWebPart public string CatalogIconImageUrl { get { return _catalogIconImageUrl; } set { _catalogIconImageUrl = value; } } public string Description { get { return _description; } set { _description = value; } } public string Subtitle { get { return _subtitle; } } public string Title { get { return _title; } set { _title = value; } } public string TitleIconImageUrl { get { return _titleIconImageUrl; } set { _titleIconImageUrl = value; } } public string TitleUrl { get { return _titleUrl; } set { _titleUrl = value; } } // Any additional properties or behaviors go here }
After this user control is defined, it can be used within a Web part zone. Remember that to use a user control in a Web page, you must first register the tag name and prefix via the Register page directive. You can then declaratively add the control via this tag name and prefix as well as set any of the Web part properties defined by the control.
<%@ Register Src="controls/LoginControl.ascx" TagName="LoginControl" TagPrefix="uc" %> ... <asp:WebPartZone ID="zoneSample" runat="server" > <ZoneTemplate> ... <uc:LoginControl ID="myLogin" runat="server" Title="Current User" Description= "Necessary to login in order to modify web parts" TitleIconImageUrl="images/webpartLogin.gif" CatalogIconImageUrl="images/webpartLogin.gif" /> </ZoneTemplate> </asp:WebPartZone>
If your Web site is to have more than two or three Web parts created from user controls, you may find that you are uncomfortable with the fact that the code-behind classes for each of these user controls looks almost identical. That is, because a Web part user control must implement IWebPart, you will find that each of these user controls have the identical data members and property definitions necessary to implement this interface. A better approach is to define a custom base class that implements the IWebPart interface, and then use it as the base class for your Web part user controls. Listing 14.5 contains a sample implementation of this custom base class.
Listing 14.5. CustomWebPartBase.cs
using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; /// <summary> /// Base class for my user control-based Web parts /// </summary> public abstract class CustomWebPartBase : UserControl, IWebPart { private string _description = ""; private string _title = ""; private string _titleIconImageUrl = ""; private string _catalogIconImageUrl = ""; private string _titleUrl = ""; // IWebPart interface requires Subtitle to be read-only; thus // to allow subclasses to initialize it, you must make it a // protected data member protected string _subtitle = ""; public string CatalogIconImageUrl { get { return _catalogIconImageUrl; } set { _catalogIconImageUrl = value; } } public string Description { get { return _description; } set { _description = value; } } public string Subtitle { get { return _subtitle; } } public string Title { get { return _title; } set { _title = value; } } public string TitleIconImageUrl { get { return _titleIconImageUrl; } set { _titleIconImageUrl = value; } } public string TitleUrl { get { return _titleUrl; } set { _titleUrl = value; } } }
Your user control code-behind classes simply inherit from this base class and set up the default values for the properties, as shown in Listing 14.6.
Listing 14.6. LoginControl.ascx.cs
using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; public partial class LoginControl : CustomWebPartBase { public LoginControl() { // Set default values for properties Title = "Current User"; Description = "Necessary to login in order to modify web parts"; TitleIconImageUrl = "images/webpartLogin.gif"; CatalogIconImageUrl = "images/webpartLogin.gif"; // IWebPart interface requires Subtitle to be read-only; // thus, must initialize it via protected data member // of CustomWebPartBase _subTitle = "Login"; } // any additional properties or behaviors go here }
Making Web Parts from Custom Controls
Although creating Web parts from user controls is quite straightforward, there may be times when you want to instead create a Web part as a custom control. The advantage of using custom controls for your Web parts is that you have complete control over the rendering and behavior of the control. As well, you can add custom verbs and make it easier to create communication links between Web parts when you create Web parts from custom controls. As well, if you want to add personalization attributes to properties within the Web part (we do this later when we work with the PropertyGridEditorPart control), you can avoid a potential deserialization error if your property uses enumerated types by using a custom Web part control.
Creating a custom Web part control is by no means difficult. You must create a new class that inherits from the WebPart class. You can then add any custom properties that your Web part requires. Finally, you must provide the rendering for the control, either by overriding the RenderContents or the CreateChildControls method. You should override RenderContents when you want to exactly control the rendered output of the Web part; when you want to use the functionality and rendering of other controls, you should override CreateChildControls. In this section's example, you use the RenderContents approach. For an example of the CreateChildControls approach, you can examine Listing 14.8 later in the chapter.
The following listing illustrates the code for a custom Web part that displays the first 10 books in the books database. Eventually, you add custom verbs to the Web part so that the user can filter the list by a publisher. The code for a basic custom control is shown in Listing 14.7.
Listing 14.7. BookListViewer.cs
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using FourLayer.ApplicationLogic; using FourLayer.BusinessEntity; namespace Chapter14 { public class BookListViewer : WebPart { private Publisher _publisher = null; public BookListViewer() { Title = "View Books"; Description = "Views all books"; TitleIconImageUrl = "images/webpartBooks.gif"; CatalogIconImageUrl = "images/webpartBooks.gif"; } /// <summary> /// Outputs the HTML content for the control /// </summary> protected override void RenderContents( HtmlTextWriter writer) { EntityCollection<Book> books; if (PublisherToUse == null) books = BookCatalogLogic.GetAllBooks(); else books = BookCatalogLogic.GetBooksByPublisher(Publisher.Id); int max = 10; if (max > books.Count) max = books.Count; string output = "<div id='listContainer'>"; for (int i = 0; i < max; i++) { Book book = books[i]; output += "<p><a href=BookDetails.aspx?id="; output += book.Isbn + ">"; output += book.Title; output += "></p>"; } output += "</div>"; writer.Write(output); } [Personalizable, WebBrowsable] public Publisher PublisherToUse { get { return _publisher; } set { _publisher = value; Title = "View Books for " + _publisher.Name; } } } }
Notice that this custom control inherits from the WebPart base class. This base class has the Web part functionality already defined. The custom control also initializes the Web part properties within its constructor. The RenderContents method outputs the actual HTML content that appears within the Web part. This method uses some classes from Chapter 11 to retrieve the data, loop through it, and output the appropriate HTML using the passed in HtmlTextWriter object. The control also defines a Publisher property, which we use later to allow the user to filter the books by a publisher. This property is marked with two attributes. The Personalizable attribute ensures that the value of this property is saved to the data store by the personalization system, whereas the WebBrowsable attribute makes this property visible to the PropertyGridEditorPart editor part (covered later in the chapter).
After this class is defined, you can then use it in a Web part page. Just as with user controls, you have to register the class in your markup through the Register directive.
<%@ Register Namespace="Chapter14" TagPrefix="ch14" %>
This Register directive is a bit different than with user controls, in that you do not specify the filename but the namespace and/or assembly name. Using the control is now simply a matter of using this tag prefix along with the class name.
<ch14:BookListViewer id="myViewer" runat="server" />
Adding a Custom Verb
To make your custom Web part control more useful, you can change it so that the user can filter the displayed books by a publisher selected by the user in the verb menu. To create a custom verb, your Web part control must override the Verbs property (which is defined by the IWebActionable interface). In your version of Verbs, you create and return a collection of WebPartVerb objects. Each of these WebPartVerb objects needs to reference the handler method that runs when the user selects the verb (from the verb menu or the verb list in the title bar).
In our example, we create a verb for each publisher in the database (obviously, this might cause some visual problems if there are many publishers and our web part zone uses WebPartVerbRenderMode="TitleBar"). The code for creating these custom verbs is as follows.
public override WebPartVerbCollection Verbs { get { // Create new collection of verbs ArrayList verbs = new ArrayList(); // Get all the publishers EntityCollection<Publisher> pubs = BookCatalogLogic.GetAllPublishers(); // Make each publisher a verb foreach (Publisher p in pubs) { WebPartVerb verb = new WebPartVerb(p.Id.ToString(), new WebPartEventHandler(ChangePublisher)); verb.Text = p.Name; verb.Description = "See books for " + p.Name; // If this is the current publisher, check it if (PublisherToUse != null && p.Name == PublisherToUse.Name) { verb.Checked = true; } // Add new verb to collection verbs.Add(verb); } // Create new verb collection with the new verbs added // to any existing verbs return new WebPartVerbCollection(base.Verbs, verbs); } }
Notice that the constructor for WebPartVerb requires a reference to the handler method that executes when the verb is selected. All your example needs to do is set the Publisher property to the name of the publisher in the verb, as shown in the following.
public void ChangePublisher(object o, WebPartEventArgs e) { WebPartVerb verb = (WebPartVerb)o; PublisherToUse = BookCatalogLogic.GetPublisherByName(verb.Text); }
The result in the browser is shown in Figure 14.13.
Figure 14.13 Custom control Web part with custom verbs
Changing the Different Display Modes
If you have been following the examples so far in this chapter, you may have wondered what exactly is the benefit of web parts. If all you want is to display content within boxes, the Web parts framework might seem to be more bother than it is worth. However, the real benefit of Web parts can be realized after you allow the user to modify your Web part pages. To do this requires that the user be able to change the Web part display mode of the page via the WebPartManager.DisplayMode property. Table 14.8 lists the available display modes for the Web parts framework.
Table 14.8. Display Modes
Name |
Description |
Browse |
Displays Web parts in the normal mode. This is the only display mode available for anonymous users. |
Catalog |
Displays the catalog user interface that allows end users to add and remove page controls. Allows dragging of controls, just as with design mode. |
Connect |
Displays the connections user interface that lets users connect Web parts. |
Design |
Makes each Web zone visible and enables users to drag Web parts to change the layout of a page. |
Edit |
Enables the Edit verb in Web parts. Choosing the Edit verb displays editing elements and lets end users edit the Web parts on the page. Also allows the dragging of Web parts, just as with design mode. |
You must provide the user interface yourself for changing the DisplayMode property. In the example that follows, we use a DropDownList (you can see the result in Figure 14.14), but you could use buttons or some other control type. You could also encapsulate the markup and event handling for this list within a user or custom control. Your definition for this list is as follows.
Figure 14.14 Moving Web parts while in Design mode
<asp:WebPartManager ID="partManager" runat="server" /> Web Part Mode: <asp:DropDownList ID="drpWebPartMode" runat="server" AutoPostBack="true" OnSelectedIndexChanged="drpWebPartMode_SelectedIndexChanged" />
Notice that we have not statically populated the list with the available modes. You should not statically fill the list because the items in the list vary depending upon the user's permissions (e.g., an anonymous user should only see the Browse mode, an authenticated user might see Design and Catalog, whereas a user with the administrator role might see the Edit mode). Instead, we programmatically fill the list when the page loads.
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // Loop through all the supported display modes foreach (WebPartDisplayMode mode in partManager.SupportedDisplayModes) { // Only display the mode if it is enabled for // the current user if (mode.IsEnabled(partManager)) { // Create list item and add to list ListItem item = new ListItem(mode.Name); // If this item matches the current display mode, // make it the selected item in the list if (mode == partManager.DisplayMode) item.Selected = true; drpWebPartMode.Items.Add(item); } } } }
The event handler for the list simply needs to change the display mode of the WebPartManager control on the page to that chosen by the user.
protected void drpWebPartMode_SelectedIndexChanged(object sender, EventArgs e) { // Retrieve the user's selection string mode = drpWebPartMode.SelectedValue; // Retrieve the WebPartDisplayMode object for this mode WebPartDisplayMode display = partManager.SupportedDisplayModes[mode]; // Set the display mode if this mode is enabled if (display != null && display.IsEnabled(partManager)) partManager.DisplayMode = display; }
Design Mode
When the Web parts page is in Design mode, each Web part zone is visible with its name and a border around it. The user can then drag and drop Web parts from one zone to another, as shown in Figure 14.14.
To provide this design functionality to your Web part page, you need a page that contains more than one WebPartZone. The following example illustrates the markup for the page shown in Figure 14.14.
<div id="sideArea"> <asp:WebPartZone ID="zoneSide" runat="server" Padding="6" WebPartVerbRenderMode="TitleBar" PartChromeType="TitleOnly" Width="14em"> <ZoneTemplate> <uc:MainMenuControl ID="myMenu" runat="server" Title="Menu"/> <uc:LoginControl ID="myLogin" runat="server" Title="Current User" /> <uc:SearchControl ID="mySearch" runat="server" Title="Search"/> <uc:CustomersControl ID="myCustomers" runat="server" Title="My Customers" /> </ZoneTemplate> </asp:WebPartZone> </div> <div id="mainArea"> <asp:WebPartZone ID="zoneMain" runat="server" WebPartVerbRenderMode="TitleBar" Padding="6" PartChromeType="TitleOnly"> <ZoneTemplate> <uc:RecentBooksControl ID="myRecentBooks" runat="server" Title="Most Recent Books" /> <uc:RSSControl ID="myAmazon" runat="server" Title="Amazon Top-Selling Computer Books" Description="Amazon Top Computer Books" XmlUrl="~/amazonRSS.xml" XsltUrl="~/RssTransformAmazon.xsl" /> <uc:RSSControl ID="myPearson" runat="server" Title="Pearson Ed News" Description="Pearson Ed News" CatalogIconImageUrl="~/images/webpartNews.gif" TitleIconImageUrl="~/images/webpartNews.gif" XmlUrl="~/pearsonRSS.xml" XsltUrl="~/RssTransformPearson.xsl" /> </ZoneTemplate> </asp:WebPartZone> </div>
Notice that the zones in this example contain no formatting markup; instead, all of the styling elements and properties are contained in a separate skin file for the theme used in the page, while the actual positioning of the zones is contained within a CSS file for the theme. Notice as well that the different web parts are all encapsulated as user controls.
Catalog Mode
Catalog mode allows the user to add Web parts to the page at runtime. It also allows the user to move parts from zone to zone in the same manner as Design mode. A catalog is simply a list of Web parts that is visible when a page is in Catalog mode.
When the page is switched to Catalog mode, the catalog user interface appears. The user interface for this catalog is provided by a CatalogZone control, which can be declaratively added to the page. The CatalogZone can only contain CatalogPart controls. Table 14.9 lists the available CatalogPart controls.
Table 14.9. CatalogPart Controls
Name |
Description |
DeclarativeCatalogPart |
Provides a way for developers to define a set of possible (but not initially visible) Web parts declaratively to a catalog. A user can select controls from the list and add them to the page, which effectively gives users the ability to change the set of controls and the functionality on a page. |
ImportCatalogPart |
Provides the user interface for a user to upload a definition file (an XML file with a .WebPart extension that imports settings for a control) to a catalog, so that the control can be added to the page. The control must be present on the server before the definition file can be imported. The description file is not the same as the control itself. It is an XML file that contains name/value pairs that describe the state of the control. |
PageCatalogPart |
Maintains references to Web parts that have been previously added to the page but have been subsequently closed by the user. This part lets the user reopen (add back to the page) closed Web parts. |
After the CatalogZone control is added declaratively to the page, CatalogPart controls can be added to the zone. For instance, in the following example, we add a catalog zone to the side area of our page that contains both a PageCatalogPart and a DeclarativeCatalogPart.
<div id="sideArea"> <asp:CatalogZone ID="catZone" runat="server" Padding="6" > <ZoneTemplate> <asp:PageCatalogPart ID="pageCatalog" runat="server" /> <asp:DeclarativeCatalogPart ID="decCatalog" runat="server" Title="Optional Parts"> <WebPartsTemplate> <uc:CalendarControl ID="myCalendar" runat="server" Title="Calendar"/> <uc:RecentViewsPartControl ID="myRecentViews" runat="server" Title="Recently Viewed Products"/> </WebPartsTemplate> </asp:DeclarativeCatalogPart> </ZoneTemplate> </asp:CatalogZone> <asp:WebPartZone ID="zoneSide" runat="server" > ... </asp:WebPartZone> </div>
Notice that the DeclarativeCatalogPart control contains two user controls. These define two optional Web parts that are not part of the initial page, but can be added later by the user via the catalog. The result in the browser is shown in Figure 14.15.
Figure 14.15 Adding parts via the catalog
The PageCatalogPart and a DeclarativeCatalogPart appear within the browser as choices within the catalog zone. For both of these parts, a list of possible Web parts appears as check boxes and labels. The user can then select a part, choose the zone, and then click the Add button. After the controls have been added to the page via the catalog, they appear in normal browse mode.
Edit Mode
The Edit design mode lets users edit or modify the properties of a Web part control. After the user switches the page to Edit mode, all Web parts in which editing is enabled display the edit verb. After the user selects the edit verb for a Web part, that Web part is then in Edit mode and the editing user interface appears for editing the selected Web part.
Similar to the approach used by Catalog mode, the user interface for editing is provided by an EditorZone control, which is declaratively added to the page. The EditorZone control can contain only EditorPart controls. Table 14.10 lists the available controls that are subclasses of the EditorPart class.
Table 14.10. EditorPart Controls
Name |
Description |
AppearanceEditorPart |
Provides a user interface for editing appearance properties of a Web part. |
BehaviorEditorPart |
Provides a user interface for editing behavioral properties of a Web part. |
LayoutEditorPart |
Provides a user interface for editing the chrome state and the zone of a Web part. This part allows non-Internet Explorer users to change the layout of Web parts. |
PropertyGridEditorPart |
Provides a user interface for editing properties of a Web part that are marked in the source code with the WebBrowsable attribute. |
Figure 14.16 illustrates how these controls might appear when rendered in the browser.
Figure 14.16 Different editor parts in the browser
After the EditorZone control is added declaratively to the page, EditorPart controls can be added to the zone. For instance, in the following example, we have added an editor zone to the main area of our page that contains an AppearanceEditorPart, BehaviorEditorPart, and a LayoutEditorPart.
<asp:EditorZone ID="editZone" runat="server" > <ZoneTemplate> <asp:AppearanceEditorPart ID="partAppear" runat="server" /> <asp:BehaviorEditorPart ID="partBehave" runat="server" /> <asp:LayoutEditorPart ID="partLayout" runat="server" /> </ZoneTemplate> </asp:EditorZone>
Making the BehaviorEditorPart Appear
However, when you view this in the browser, only the AppearanceEditorPart and the LayoutEditorPart are actually visible. The reason for this is that changes made to the properties of a Web part via the BehaviorEditorPart affect not only the current user but all users. As such, the user must have shared scope before the BehaviorEditorPart control is visible.
You may recall from the earlier discussion on Web part personalization that, with shared scope, any changes that the user makes to the state of a Web part are visible to all users. By default, all users are denied access to shared scope. It might make sense, however, for site administrators to have shared scope so that they can make universal changes to a Web part with the BehaviorEditorPart.
There are two steps you must follow to have the BehaviorEditorPart control visible in your pages. First, you must allow users in the administrator role to have access to shared scope. You do this by adding the following section to your Web.config file.
<system.web> ... <webParts> <personalization> <authorization> <allow roles="Administrator" verbs="enterSharedScope"/> </authorization> </personalization> </webParts> </system.web>
Rather than allowing shared scope on a role basis, you could instead do so on a user-by-user basis.
<allow users="Hamlet,Plato" verbs="enterSharedScope"/>
The next step is to change the personalization scope of the page from user to shared scope. Perhaps the easiest way to do this is to add the following to the Page_Load method of the page.
protected void Page_Load(object sender, EventArgs e) { ... // For users who are authorized to enter shared // personalization scope (that is, users who are allowed to // make changes that affect all users), change the page scope // from user scope to shared scope if (partManager.Personalization.Scope == PersonalizationScope.User && partManager.Personalization.CanEnterSharedScope) { partManager.Personalization.ToggleScope(); } }
Using the PropertyGridEditorPart
The PropertyGridEditorPart is a bit different from the other editor parts in that it does not present the user with a predefined set of properties. Instead, the PropertyGridEditorPart allows the developer to display a list of custom properties to the user. This control displays all the properties in the Web part that have been marked with the WebBrowsable attribute (thus, the Web part has to be a user control or a custom control). When a property is marked with this attribute, a PropertyGridEditorPart creates the editing user interface based on the type of the property. For instance, strings, dates, and numbers are displayed in a TextBox, Booleans are displayed with a CheckBox, whereas enumerated types are displayed in a DropDownList.
You can add some other attributes to the properties of your Web part class to further customize how the control displays the editing interface. These include
- WebDisplayName—Allows you to specify the text for the label that appears with each control in the editing interface.
- WebDescription—Allows you to specify a string that appears as a ToolTip for each control.
- Personalizable—Allows you to specify the personalization scope for the property. If set to PersonalizationScope.Shared, the property is only displayed for users in shared scope (e.g., administrators).
Listing 14.8 contains a sample Web part custom control that illustrates the use of some of these attributes. This Web part displays an RSS feed. Unlike the custom control created in Listing 14.7, this one does not override the RenderContents method, but instead overrides the CreateChildControls method. The CreateChildControls method allows you to programmatically create and set up existing server controls and then add them to the child control collection of the Web part; by doing so, you can let the child controls handle their own rendering. In the example shown in Listing 14.8, the CreateChildControls method uses an Xml server control along with an XSLT file to read and display several sample external RSS feeds. This control also presents two WebBrowsable properties: one for specifying which publisher's RSS feed to use, the other the filename of the XSLT file to use. The former is modifiable by all users, whereas the later is only available to administrators (i.e., users with shared scope).
Listing 14.8. PressReleaseRSS.cs
using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; namespace Chapter14 { /// <summary> /// Specifies the possible press release RSS feeds /// </summary> public enum PressReleaseRssFeeds { AddisonWesley, PeachPit, PrenticeHall, QuePublishing, SamsPublishing } public class PressReleaseRSS: WebPart { private string _xsltUrl = "~/RssTransformPressRelease.xslt"; private PressReleaseRssFeeds _whichFeed = PressReleaseRssFeeds.AddisonWesley; // For simplicity's sake, we will hardcode the URLs for the // RSS feed, but it is better and more realistic // to extract from database or config file private string[] _rssUrls = { "http://www.awprofessional.com/press/press_rss.asp", "http://www.peachpit.com/press/press_rss.asp", "http://www.phptr.com/press/press_rss.asp", "http://www.quepublishing.com/press/press_rss.asp", "http://www.samspublishing.com/press/press_rss.asp" }; public PressReleaseRSS() { Title = "Press Releases"; Description = "Press releases from Pearson Ed"; TitleIconImageUrl = "images/webpartNews.gif"; CatalogIconImageUrl = "images/webpartNews.gif"; } [Personalizable(PersonalizationScope.Shared), WebBrowsable, WebDisplayName("XSLT filename")] public string XsltUrl { get { return _xsltUrl; } set { _xsltUrl = value; } } [Personalizable(PersonalizationScope.User), WebBrowsable, WebDisplayName("Press Release RSS Feed")] public PressReleaseRssFeeds WhichFeed { get { return _whichFeed; } set { _whichFeed = value; } } /// <summary> /// Responsible for creating the content of the control /// </summary> protected override void CreateChildControls() { // Create an XML Web server control Xml myXml = new Xml(); // Get URL for selected feed string xmlurl = _rssUrls[(int)WhichFeed]; // Create the XPathNavigator object XPathDocument xpdoc = new XPathDocument(xmlurl); XPathNavigator xnav = xpdoc.CreateNavigator(); // Set up the XML control and apply XSLT transformation myXml.XPathNavigator = xnav; myXml.TransformSource = HttpContext.Current.Server.MapPath(XsltUrl); // Add XML control to the Web part's collection // of child controls Controls.Add(myXml); } } }
With this control defined, you can now add it to a Web part page.
<%@ Register Namespace="Chapter14" TagPrefix="ch14" %> ... <ch14:PressReleaseRSS ID="myPressRelease" runat="server" Title="Press releases" Description="Press releases from Pearson Ed" CatalogIconImageUrl="~/images/webpartNews.gif" TitleIconImageUrl="~/images/webpartNews.gif" WhichFeed="AddisonWesley" XsltUrl="~/RssTransformPressRelease.xsl" />
Figure 14.17 illustrates how the PropertyGridEditorPart editor part appears for the PublisherRSSControl Web part depending upon the scope of the user.
Figure 14.17 PropertyGridEditorPart in the browser
Web Part Connections
The Web parts framework allows connections to be made between Web parts. When a part is enabled for connections, a user can create dynamic communication connections between the parts at runtime, as well as static, predefined connections declared in the markup of the page. You can also declare a user interface that enables users to manage connections and to connect or disconnect Web parts at runtime.
A Web parts connection is a link between two Web parts that enables them to share data. A connection always involves exactly two parts: one is the provider of data, and the other is the consumer of the data from the provider. A provider Web part can establish connections with multiple consumers at the same time. A consumer Web part connects to only one provider at a time. Any changes made to a provider are immediately consumed by any Web part consumers. Figure 14.18 illustrates a sample usage of Web parts connections. The provider in this example is the Select Publisher Web part; whenever the user selects a different publisher, the two consumer Web parts (View Books and News) are updated and display new data based on the user's selection.
Figure 14.18 Web part connections
Creating a Static Connection
A Web part connection is either static or dynamic. Static connections are declared in the markup of the page by adding a StaticConnection element to the WebPartManager. Creating a static connection requires that you complete these four steps.
- Define a C# interface that is the shared connection contract between the two Web parts. This interface defines the data that is passed between the two parts.
- The provider Web part must implement this connection interface. As well, the provider must define a connection point. A connection point is a method that returns an instance of the class that implements the connection interface; in other words, it must return the provider object itself. This method must be marked with the ConnectionProvider attribute.
- The consumer Web part must also define a connection point. In this case, it is a void method that accepts an object of the type defined by the interface from step 1. This object can then be used by the consumer to retrieve data from the provider Web part. This method must be marked with the ConnectionConsumer attribute.
- You must declaratively define the static connections in the markup of the Web parts page by adding a StaticConnection element to the WebPartManager.
Figure 14.19 provides a visualization of how data flows via connection points between provider and consumer Web parts.
Figure 14.19 Data flow with Web part connections
Walkthroughs 14.1 through 14.3 demonstrate how to set up two Web part connections. These create the controls and the connections necessary to implement the example shown in Figure 14.18.
After examining the code for these sample provider and consumers Web parts, you may wonder when the connection between the provider and consumer is made during the page's lifecycle. It is made during the page's LoadComplete event. This event fires after all postback data and view-state data is loaded into the page and its controls. This means that your page cannot rely on the connection within the page's PreInit, Init, PreLoad, or Load event handlers.
Making Static Connections with Master Pages
When I introduced the Web parts framework, I mentioned that each page must have one and only one WebPartManager. This can be a problem for some designs, particularly those using master pages. When designing a site with master pages, it is usual to put elements that are common to all pages within a master page. In such a situation, it might make sense to place the WebPartManager within the master page. How then can you define Web part connections in your content pages if the WebPartManager exists in a separate file?
The ProxyWebPartManager control exists for this particular situation in which you need to declare static connections in content pages when a WebPartManager control has already been declared in a master page. The ProxyWebPartManager control takes the place of the WebPartManager control in this scenario. By declaring a ProxyWebPartManager element instead of a WebPartManager element within content pages, you can still define static connections, as shown in the following example.
<asp:ProxyWebPartManager ID="myProxy" runat="server"> <StaticConnections> <asp:WebPartConnection ID="conn3" ProviderID="someProvider" ConsumerID="someConsumer" /> </StaticConnections> </asp:ProxyWebPartManager>
At runtime, the connections in the ProxyWebPartManager control are simply added to the StaticConnections collection of the WebPartManager control and treated like any other connection.
Creating a Dynamic Connection
Although static connections make sense for most situations, there are times when a page is unable to create static connections. For instance, recall that it is possible for the user to add a Web part that requires a connection via the Web part catalog. In such a case, you are unable to statically predefine the connection because static connections can only be made between Web parts that are declared within WebPartZone elements. Another situation in which you might need to use dynamic connections is a page whose Web parts have been programmatically added.
To allow the user to make dynamic connections, you must still follow the first three steps indicated in the section on setting up static connections. Instead of adding the declarative StaticConnections element, however, all you must do here as a developer is add a ConnectionsZone control to the page. This control can be styled like the other Web parts controls. After this control is added to the page, the user must follow these steps for each connection.
- Change the page to Connect mode (of course, this presumes that the page contains some mechanism for doing so).
- From the consumer Web part, choose the Connect verb. This displays the Connection Zone part.
- In the Connection Zone part, the user must choose the provider connection from the list of available provider connections for the page and click the Connect button. This connects just the two parts.
- The user can optionally disconnect the connection or close the Connection Zone.
Figure 14.20 illustrates how this process appears in the browser.
Figure 14.20 Making a dynamic connection