In this section we aren't going to develop a heavy infrastructure, as ASP.NET 2.0 already provides that for us! You can think of a Web Part as being similar to a user or custom control, with the addition of a title bar and a border (both of which are optional), and a drop-down menu with options to minimize and restore the Web Part, close it, delete it, and edit it. These commands are not always available and visible in the context menu, as this depends on the display mode in which the host page was loaded. The available display modes are as follows:
Browse: In this mode the Web Part's context menu lists commands to minimize the Web Part so that only the title part will be visible, to restore it (if minimized), and to close it so it completely disappears from the page.
Design: In this mode you can move Web Parts around, from one Web Part zone to another, by dragging and dropping them visually. Web Part zones are rectangular areas on the page where a Web Part can be dropped — only content in these areas can be personalized. A zone can be thought of as being a parking place on the page where Web Parts can reside: You can have a number of zones on one page. The Web Part's context menu also has a command to completely delete the Web Part from the page. The Delete command is different from Close, because Close just hides the Web Part from the page, but doesn't really remove it, so that you can re-open it later.
Edit: In this mode an Edit command appears in the Web Part's context menu. When clicked, it shows editor boxes used to change the Web Part's standard and custom properties.
Catalog: In this mode the page displays a catalog of available Web Parts that can be added to the page. There is a page catalog, and a more general catalog. The former lists Web Parts that are already present on the page, but are not visible because they have been closed by the user. The latter is a catalog that generally includes all Web Parts available for the entire site.
Connect: In this mode the Web Part's context menu has a Connect item that allows the user to create a relationship between two Web Parts that implement the proper interfaces and use the proper attributes (a Web Part must be explicitly designed to support connection to another Web Part), so that they exchange information between each other. This allows users to configure master/detail views, where, for example, a Web Part listing categories is connected to another one that lists the products of the selected category. The Web Part listing products (which is a detail view under Categories) may itself be a master to another Web Part showing more details about the selected product. The Web Part listing products would be a consumer of categories in the first case and a producer of part details in the second case.
Important |
All modes except the Browse mode are only available to registered and authenticated users. You can then specify which users or roles can design and edit the shared view, in addition to their own personal view. |
ASP.NET's Web Part Framework is very easy and intuitive to work with, and provides a collection of controls to create Web Part zones, the Web Part catalogs, and the editor boxes, with just a few lines of code. A Web Part is written as a traditional user or a custom control, with just a few additional attributes; you can quickly turn an existing control into a Web Part once you add personalization to a site. In the first part of the "Design" section, you'll learn more about the various controls of this framework and how to put all the pieces to work together, and later you'll learn how to actually use Web Parts in our sample site.
As mentioned earlier, a Web Part can be developed as a user control (which is like a partial page, with markup code and a code-behind) or a custom control (which is a class written in C# for which you create the output 100% programmatically). The choice between the two depends on your needs and requirements, and it's much like the choice between writing user and custom controls in general. If you want to compile everything into an assembly — so that the source code is protected and the output cannot be modified by an external developer and shared among multiple web sites by installing it in the GAC — then you'll want to go with custom controls. If, instead, you don't care much about those aspects, but prefer simplicity and speed of development, and the ease of changing the appearance of the Web Part by working with markup code instead of with C# code, then user controls will be your best bet. In this section, I'll provide a quick overview of both approaches.
For our first example we'll build a simple online calculator, with two textboxes for the operands, a button to submit the form and do the calculation, and a label to show the result. You define the UI with the usual markup code, in the .ascx file (typical of user controls):
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Calculator.ascx.cs" Inherits="Calculator" %> Op1: <asp:TextBox ID="txtOp1" runat="server" /><br /> Op2: <asp:TextBox ID="txtOp2" runat="server" /><br /> <asp:Button ID="btnCalc" runat="server" Text="Calculate" OnClick="btnCalc_Click" /><br /> <asp:Label ID="lblResult" runat="server" />
The code-behind needs a property that specifies the type of operation to perform: addition, subtraction, division, or multiplication. The property is named Operation, and is of type OperationType, an enumeration that contains the values indicated above. Then, when the button is clicked, you simply retrieve the input strings, convert them to integers, perform the specified operation, and show the result (I'm not showing the type checks and other validations in order to keep this simple):
public enum OperationType : int { Addition, Subtraction, Division, Multiplication } public partial class Calculator : System.Web.UI.UserControl { private OperationType _operation = OperationType.Addition; public OperationType Operation { get { return _operation; } set { _operation = value; } } protected void btnCalc_Click(object sender, EventArgs e) { int op1 = Convert.ToInt32(txtOp1.Text); int op2 = Convert.ToInt32(txtOp2.Text); if (this.Operation == OperationType.Addition) lblResult.Text = (op1 + op2).ToString(); else if (this.Operation == OperationType.Subtraction) lblResult.Text = (op1 - op2).ToString(); if (this.Operation == OperationType.Division) lblResult.Text = ((double)op1 / (double)op2).ToString(); else lblResult.Text = (op1 * op2).ToString(); } }
So far this is a 100% standard user control. Even so, it can be used as a Web Part on a page. Actually, any control can be used as a Web Part: ASP.NET will take care of wrapping it into a GenericWebPart at runtime. However, to enable users to personalize it by setting the properties, you need to add some attributes to the public properties that you want to make editable at runtime. In particular, the WebBrowsable attribute specifies that the property will be visible in the Web Part's Editor box; the Personalizable attribute specifies whether the property is editable (either for the shared view or also at the user level in the personal view); the WebDisplayName specifies the property title in the editor box, so that it's more understandable and user friendly; and the WebDescription attribute is a longer description of the property. In this example, you would decorate the Operation property with attributes as follows:
private OperationType _operation = OperationType.Addition; [Personalizable(PersonalizationScope.User), WebBrowsable, WebDisplayName("Operation Type"), WebDescription("The type of operation performed when submit is clicked.")] public OperationType Operation { get { return _operation; } set { _operation = value; } }
Because this is just a normal user control that ASP.NET will wrap with GenericWebPart, it doesn't specify Web Part–specific attributes such as the title that should be listed on the Web Parts catalog, or an icon. You can add those attributes to a user control by implementing the IWebPart interface, which defines properties with self-descriptive names such as Title, Description, TitleIconImageUrl, CatalogIconImage, and TitleUrl. You implement these properties as simple wrappers for private fields, as follows:
public partial class Controls_Calculator : System.Web.UI.UserControl, IWebPart { private string _catalogIconImageUrl = ""; public string CatalogIconImageUrl { get { return _catalogIconImageUrl; } set { _catalogIconImageUrl = value; } } private string _description = ""; public string Description { get { return _description; } set { _description = value; } } protected string _subTitle = ""; public string Subtitle { get { return _subTitle; } set { _subTitle = value; } } protected string _title = "Online Calculator"; public string Title { get { return _title; } set { _title = value; } } private string _titleIconImageUrl = ""; public string TitleIconImageUrl { get { return _titleIconImageUrl; } set { _titleIconImageUrl = value; } } private string _titleUrl = ""; public string TitleUrl { get { return _titleUrl; } set { _titleUrl = value; } } // ...the rest of the class as shown earlier... }
Note that the _title field is initialized with "Online Calculator", which is the string that will be shown in the Web Part catalog to refer to this Web Part, and on the title bar of the Web Part itself when added on the page. You'll learn how to build and use the catalog shortly.
Creating Web Parts as user controls is fine in many situations, and especially when the Web Part is very specific to your site and you don't plan to reuse it on other projects and redistribute it. As you should already know, custom controls (as opposed to user controls) are better when you want to have everything (both the UI and the logic behind it) compiled into a single assembly that's easier to move around and install into the GAC, so that you can reutilize the Web Part in multiple sites on the same server. However, even if the Web Part is site-specific, and you don't need to protect its UI against modifications by other developers, there's still another reason why you might prefer a custom control over a user control: When writing a Web Part as a custom control, you can make the class inherit from the System.Web.UI.WebControls.WebParts.WebPart base class, which enables you to initialize all the IWebPart interface's properties mentioned above, plus many others that specify whether the Web Part can by minimized, closed, moved around, edited, and so on: AllowMinimize, AllowClose, AllowZoneChange, AllowEdit, and ChromeType (border and title bar). ExportMode indicates whether the Web Part's settings can be saved to a local file (serialized) by the user (to be restored later in case the Web Part is reset), AuthorizationFilter specifies which users and roles can see it on the page, Verbs specifies the list of items in the Web Part's drop-down menu, and more. I won't show you all these possibilities here, but the following example shows a Web Part that displays the current date, whose format the user can change by means of the Format property (which is decorated with the same attributes described earlier in the section about user controls). The Web Part's output is rendered from inside the override of the RenderContents method, as you would do with any custom control. The Verbs property is also overridden to add a new item on the Web Part's menu, which when clicked executes some client-side JavaScript code that calculates and displays the time elapsed since 01/01/1970:
public class WhatTimeIsIt : WebPart { public WhatTimeIsIt() { this.Title = "What Time Is It?"; } private string _format = "T"; [Personalizable(PersonalizationScope.User), WebBrowsable, WebDisplayName("Date/Time Format"), WebDescription("The format of the current date & time.")] public string Format { get { return _format; } set { _format = value; } } protected override void RenderContents(HtmlTextWriter writer) { writer.Write(DateTime.Now.ToString(this.Format)); } public override WebPartVerbCollection Verbs { get { // We're specifying JavaScript to calculate the no. of days WebPartVerb popupDaysItem = new WebPartVerb("popupDaysItem", @"var d, s, t; var MinMilli = 1000 * 60; var HrMilli = MinMilli * 60; var DyMilli = HrMilli * 24; d = new Date(); t = d.getTime(); s = 'It has been ' s += Math.round(t / DyMilli) + ' days since 01/01/1970'; alert(s);"); popupDaysItem.Text = "Popup days since 1970"; popupDaysItem.ImageUrl = "~/Images/Clock.gif"; WebPartVerb[] verbs = new WebPartVerb[] { popupDaysItem }; return new WebPartVerbCollection(verbs); } } }
The WebPartVerb class also has a ServerClick event that allows you to process the click on the new menu item on the server, if that function can't be done on the client. You can also customize its appearance with an icon on the left of the caption, by means of the ImageUrl property. You'll see how this Web Part appears on the page in the next section.
Now that you have a couple of Web Parts, you can build a simple page that supports most of the basic features of the Web Part Framework. The built-in controls provided by ASP.NET used in this section are those listed under the WebParts tab of Visual Studio's Toolbox:
WebPartManager: This control manages the current display mode, i.e., it allows you to switch between browse mode, design mode, edit mode, etc. It also allows you to change the current scope from user to shared view. The control does not provide any user interface per se; you have to build the UI yourself, and then call the WebPartManager's method to change the display mode, or to programmatically add Web Parts on the page. There must be one and only one WebPartManager on the page, and it must be declared before any other control listed here; otherwise, you will get a runtime exception.
WebPartZone: This control defines an area where Web Parts can be added programmatically, or dragged and dropped at runtime by the user. They are generally put inside table cells or <div> containers, which define the page's layout.
CatalogZone: This control defines a region that will contain particular Web Parts (listed below) that create the catalog of Web Parts, declared on the page or already present on the page. This area is invisible until the page enters the catalog display mode.
DeclarativeCatalogPart: This Web Part must be placed within the CatalogZone, and when the page is in catalog mode it shows a list of Web Parts declared at design time, and allows the user to insert one or more of them into a selected Web Part zone.
PageCatalogPart: This Web Part must be placed within the CatalogZone, and when the page is in catalog mode it displays a list of Web Parts already present on the page but closed, and therefore not currently visible.
ImportCatalogPart: This Web Part allows you to import a Web Part from a configuration file saved on the user's local computer, which is uploaded to the server. The file contains the URL of the Web Part to insert on the page, and the values for its properties. The file is generated by the Export command of the Web Part's menu. Note that the export is disabled by default, and must be explicitly enabled (covered later).
EditorZone: This control defines a region that will contain particular Web Parts (listed below) that create the editor of Web Parts. This area is invisible until the page enters the edit display mode, and the Edit command for a specific Web Part is clicked.
AppearanceEditorPart: This Web Part must be placed within the EditorZone, and allows you to edit the Web Part's properties related to its appearance, such as its title, the chrome type, and the size.
BehaviorEditorPart: This Web Part must be placed within the EditorZone, and allows you to edit the Web Part's properties related to its behavior, such as whether the Web Part can be closed, minimized, or moved from one Web Part zone to another, and the URL to which users are redirected when they click the Web Part's title bar.
LayoutEditorPart: This Web Part must be placed within the EditorZone, and allows you to edit the Web Part's properties related to its layout, such as its state (minimized or opened) and the zone where it is located.
PropertyGridEditorPart: This Web Part must be placed within the EditorZone, and allows you to edit all the custom properties that have the WebBrowsable attribute. A default user interface is automatically built according to the type of the property. For example, Boolean properties are rendered as a checkbox, enumerations are displayed in a DropDownList, and other types are edited by means of a textbox. If you don't like the standard UI for a particular property, however, you could build your own editor and associate it to the property. A custom editor is just a custom control that inherits from EditorPart. On the Web Part, you must override the WebBrowsableObject property and the CreateEditorParts method, which returns the collection of custom editor parts to show in the EditorZone.
ConnectionsZone: This Web Part creates the UI for connecting two Web Parts; one acts as a provider and the other as a consumer for the value being exchanged. The UI is displayed only when the page's display mode is correct.
It only takes a few minutes to build a sample page to test the appearance and behavior of the Web Part examples we made earlier. You can do everything from the visual designer: Draw a table with a first row that has a single cell spanning two columns, and put a WebPartZone inside it. Split a second row into two cells and insert a WebPartZone inside each of them. Then, on the first row, create two more columns that span two rows: Insert an EditorZone inside the first one, and drop all available EditorParts inside its surface; then do the same with the CatalogZone and the catalog parts on the last column. At the top of the page add a WebPartManager control, and a few LinkButton controls to activate the various display modes on the page. Finally, spice up the appearance of the page a bit, by applying one of the pre-built styles to the controls, from the controls' Smart Tasks Auto Format dialog window. Figure 10-1 shows the final page as displayed by the graphical designer.
In order to add the Web Parts to the DeclarativeCatalogPart, you can just drag and drop them on its surface. You can do that with a user control by taking it directly from the Solution Explorer. For custom controls, you should compile them and add them to the Toolbox. Otherwise, you will need to declare them manually from the page's source code.
Important |
You can add Web Parts directly to WebPartZone controls, so they are created by default in the shared view, without the need for an editor to put them in place at runtime. |
Following is the page's source without the various appearance attributes added to the Web Part controls after using the Auto Format command on them (the code that references the Web Parts and then registers them in the catalog is marked in bold):
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="WebParts.aspx.cs" Inherits="Test_WebParts" %> <%@ Register Src="Calculator.ascx" TagName="Calculator" TagPrefix="mb" %> <%@ Register Namespace="MB.TheBeerHouse.UI.Controls" TagPrefix="mb" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Web Parts Test</title> </head> <body> <form id="form1" runat="server"> <div> <div style="text-align: right;"> <asp:WebPartManager ID="WebPartManager1" runat="server" /> <asp:LinkButton ID="btnBrowseView" runat="server" OnClick="btnBrowseView_Click">Browse View</asp:LinkButton> | <asp:LinkButton ID="btnDesignView" runat="server" OnClick="btnDesignView_Click">Design View</asp:LinkButton> | <asp:LinkButton ID="btnEditView" runat="server" OnClick="btnEditView_Click">Edit View</asp:LinkButton> | <asp:LinkButton ID="btnCatalogView" runat="server" OnClick="btnCatalogView_Click">Catalog View</asp:LinkButton> <asp:Label runat="server" ID="panPersonalizationModeToggle"> | <asp:LinkButton ID="btnPersonalizationModeToggle" runat="server" OnClick="btnPersonalizationModeToggle_Click">Switch Scope (current = <%= WebPartManager1.Personalization.Scope.ToString() %>) </asp:LinkButton> </asp:Label> </div> <table width="100%"> <tr> <td colspan="2" valign="top"> <asp:WebPartZone ID="TopZone" runat="server" Width="100%" HeaderText="Top Zone" Height="100%"> </asp:WebPartZone> </td> <td rowspan="2" valign="top"> <asp:EditorZone ID="EditorZone1" runat="server" Width="100%"> <ZoneTemplate> <asp:PropertyGridEditorPart ID="PropertyGridEditorPart1" runat="server" /> <asp:AppearanceEditorPart ID="AppearanceEditorPart1" runat="server" /> <asp:BehaviorEditorPart ID="BehaviorEditorPart1" runat="server" /> <asp:LayoutEditorPart ID="LayoutEditorPart1" runat="server" /> </ZoneTemplate> </asp:EditorZone> <asp:CatalogZone ID="CatalogZone1" runat="server"> <ZoneTemplate> <asp:PageCatalogPart ID="PageCatalogPart1" runat="server" /> <asp:DeclarativeCatalogPart ID="DeclarativeCatalogPart1" runat="server"> <WebPartsTemplate> <mb:calculator id="Calculator1" runat="server" /> <mb:WhatTimeIsIt id="currDateTime" runat="server" /> </WebPartsTemplate> </asp:DeclarativeCatalogPart> </ZoneTemplate> </asp:CatalogZone> </td> </tr> <tr> <td width="50%" valign="top"> <asp:WebPartZone ID="LeftZone" runat="server" Width="100%" HeaderText="Left Zone" Height="100%"> </asp:WebPartZone> </td> <td width="50%" valign="top"> <asp:WebPartZone ID="RightZone" runat="server" Width="100%" HeaderText="Right Zone" Height="100%"> </asp:WebPartZone> </td> </tr> </table> </div> </form> </body> </html>
The code-behind is very simple: You just handle the Click event of the LinkButton controls declared above, and call the methods of the WebPartManager control to switch to one of the available display modes. Every time the page loads you also check whether the modes are actually available (they might not be, depending on the current user's permissions), by checking for their existence in the WebPartManager's SupportedDisplayModes collection, and then hide them if they are not:
public partial class Test_WebParts : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) UpdateUI(); } protected void UpdateUI() { btnBrowseView.Enabled = WebPartManager1.SupportedDisplayModes.Contains( WebPartManager.BrowseDisplayMode); btnDesignView.Enabled = WebPartManager1.SupportedDisplayModes.Contains( WebPartManager.DesignDisplayMode); btnEditView.Enabled = WebPartManager1.SupportedDisplayModes.Contains( WebPartManager.EditDisplayMode); btnCatalogView.Visible = WebPartManager1.SupportedDisplayModes.Contains( WebPartManager.CatalogDisplayMode); panPersonalizationModeToggle.Visible = WebPartManager1.Personalization.CanEnterSharedScope; } protected void btnBrowseView_Click(object sender, EventArgs e) { WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode; UpdateUI(); } protected void btnDesignView_Click(object sender, EventArgs e) { WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode; UpdateUI(); } protected void btnEditView_Click(object sender, EventArgs e) { WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode; UpdateUI(); } protected void btnCatalogView_Click(object sender, EventArgs e) { WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode; UpdateUI(); } protected void btnPersonalizationModeToggle_Click(object sender, EventArgs e) { WebPartManager1.Personalization.ToggleScope(); UpdateUI(); } }
Figure 10-2 shows how the page will look like at runtime, when in catalog mode.
Figure 10-3 shows the page in browse mode after adding both Web Parts. It also shows the drop-down menu of the WhatTimeIsIt Web Part, with its custom-made option that uses client-side JavaScript to pop up a message.
If you click the Minimize command (which is available in any display mode), the Web Part will show only the title bar, as shown in Figure 10-4 (the WhatTimeIsIt control is minimized). When in that state, the Web Part's menu will replace the Minimize command, and the Restore command will re-open the Web Part's box.
Figure 10-5 is the most spectacular because it shows the page in design mode as I was dragging a Web Part from one zone to another (don't confuse the Web Part design mode with Visual Studio's design mode — Web Parts have a runtime design mode to enable users to edit their configuration). The Web Part being dragged shows as a semi-transparent floating layer, which gives you clear feedback about what you're dragging and what space it is going to occupy after the drop.
Note |
The drag and drop feature is not supported by non-IE browsers. It doesn't work with Firefox, and if you want to move a Web Part you must activate the edit mode for it and select the destination zone from the Layout editor (see Figure 10-7). |
To edit a Web Part you must first enter the respective mode by clicking the link at the top of the page, and then click the Edit option from the drop-down menu of the specific Web Part, as shown in Figure 10-6.
Figure 10-7 shows the page while editing the Online Calculator Web Part. Note how the Operation custom property is editable by means of a DropDownList control created by the PropertygridEditorPart control.
When a user adds a Web Part on the page, for herself or for the shared view, and when she edits the properties of a Web Part, these personalizations of the page are stored persistently, so that the user will find the same configuration she left after her last visit, even if she closes the browser and returns a week later. Where this information is stored depends on the data access provider, because the Web Part Framework is based on the same provider model design pattern used by the membership and roles system, the logging framework, the profile module, and so on. The framework includes a single built-in provider for Web Parts management that targets SQL Server, but you could, of course, implement your own provider to save personalization information in XML files, an Oracle database, or somewhere else. As for all other ASP.NET modules and subframeworks, the configuration is located in the web.config file, and in this case the section is <webParts>. It has an enableExport attribute that enables users to export the configuration of a single Web Part to a local XML file, so that they can later restore it in case they reset or change the page during some tests to find the perfect configuration. This feature is disabled by default. There is then an inner section called <personalization> that specifies which provider to use to save/load information from those registered under the <providers> subsection. Under the <personalization> section there's also the <authorization> section, which defines which user roles are allowed to enter the shared scope, and thus modify the page and its Web Parts for all users. Here's a sample configuration, showing how to set up the Web Part Framework in TheBeerHouse site:
<webParts enableExport="true"> <personalization defaultProvider="TBH_PersonalizationProvider"> <providers> <add name="TBH_PersonalizationProvider" connectionStringName="LocalSqlServer" type="System.Web.UI.WebControls.WebParts.SqlPersonalizationProvider, System.Web, Version=2.0.0.0, Culture=neutral, azxmPublicKeyToken=b03f5f7f11d50a3a" /> </providers> <authorization> <allow roles="Administrators,Editors" verbs="enterSharedScope" /> </authorization> </personalization> </webParts>
For your reference, Figure 10-8 shows which tables are used to store personalizations on the SQL Server database, and the relationships between them.
Personalization is always at the page level, which means that even if you put all the Web Part zones and the WebPartManager on a master page, different pages that use that same master page will have a different configuration, because the data is saved in different records according to the specific content page's URL. Personalization for the shared views and for the per-user views are saved in two separate tables, but in both cases there's a PageSettings image field that stores all the information in binary format.
Now that we've explored the Web Part Framework in general and looked at some examples, we can design the Web Parts for TheBeerHouse. This task is simple because, as you've seen, the framework does practically everything for you. First of all, you must decide which content you want to make personalizable, i.e., which controls will be turned into Web Parts. The perfect candidates are the PollBox control (developed in Chapter 6), which displays a poll with the given ID or the poll marked as the current one, and the RssReader control (developed in Chapter 5), which can render the RSS feed read from any URL. The RssReader has been used already more than once to read our own feeds for the latest articles, for the latest forum threads, and for the most active threads, but you could use it to display the feed offered by any other external site (maybe friendly sites you've partnered with to exchange information). Finally, the welcome text and graphics of the home page will also be put into a separate user control (WelcomeBox), so users will be able to close, minimize, or completely remove it from the page, and perhaps place the RSS feeds in a more visible position. All properties of the RssReader will be made personalizable by the user, but properties of the PollBox Web Part will be made accessible only for the shared view, so that only editors and administrators can set them. Both controls were implemented as user controls, so you can just "upgrade" them to a Web Part by adding the WebBrowsable, Personalizable, WebDisplayName, and WebDescription attributes to their properties. You also need to implement the IWebPart interface, so that you can set the Web Part's caption shown in the title bar and the catalog box. Instead of implementing IWebPart separately for each of the three Web Parts, you can just implement it in a common base class that inherits from System.Web.UI.UserControl, and then make the user controls' code-behind class inherit from this custom base class instead of from UserControl directly. This way you'll only implement IWebPart once.
The other important thing is deciding where, and how, to place all the necessary Web Part—related controls on the pages to which you want to add personalization support. Personalization is not something you'll want to add everywhere because it doesn't make much sense to move and change content in the page showing an article or a forum thread. However, you may want to support it in a few other pages in addition to the home page — for example, in the pages listing article categories (ShowCategories.aspx) and subforums (ShowForums.aspx). Instead of placing the WebPartManager, the WebPartZone controls, the editors, and the catalog controls on every page, you'll add all them just once, in the template .master page shared by all pages of the site. Figure 10-9 graphically represents where you'll declare all controls and zones. As you can see, there are parts with fixed content, with Web Part zones placed above and below them, in each of the three columns; in the larger central column there are also two sections where the available space is split into two columns, with a Web Part zone in each of them, so that you can put two or more Web Parts side by side.
The master page will have a custom EnablePersonalization property, which will be set to true only on those content pages that should support personalization: By doing this the controls used to switch the display mode will become visible; otherwise, they will be hidden. You've already seen in the previous section how to restrict the permissions for entering the shared scope and modifying the page from all users to users who belong to the Administrators or Editors roles.