Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
1058 CHAPTER 31 ■ PORTALS WITH WEB PART PAGES
Figure 31-8. The previously made changes in action
Implementing the IWebPart Interface
Until now you have accessed WebParts from the outside only. But when creating a user control that will be used as a WebPart on a web part page, you can access properties of the WebPart from inside the user control as well. To a certain degree, you can control the WebPart’s appearance and behavior in a more detailed manner by implementing the IWebPart interface.
The IWebPart interface defines a contract between your control (a server control or user control), which is used by the GenericWebPart wrapper class to communicate with your control for specific things such as automatically retrieving a control’s title so that you don’t need to set it from outside every page where you are going to use this WebPart. Table 31-3 lists the members you have to provide in your web part when implementing the IWebPart interface.
Table 31-3. The Members of the IWebPart Interface
Member |
Description |
CatalogImageUrl |
Gets or sets the URL to an image displayed for the WebPart in the |
|
PageCatalogPart of a CatalogZone. |
Description |
Gets or sets a string that contains a user-friendly description of the |
|
WebPart. |
Subtitle |
Specifies the user-friendly subtitle of the web part. |
Title |
Specifies a title displayed for the web part. With this property specified, |
|
you don’t need to set the title from outside as previously described. |
TitleIconImageUrl |
URL that points to an image displayed as an icon within the title bar of the |
|
WebPart. |
TitleUrl |
Specifies the URL to which the browser should navigate when the user |
|
clicks the title of the WebPart. If this URL is set, the title renders as a link; |
|
otherwise, the title renders as static text. |
|
|
CHAPTER 31 ■ PORTALS WITH WEB PART PAGES |
1059 |
As you can see, implementing this interface is not too much work. You can now implement the interface in the previously created Customers web part as follows:
public partial class Customers : System.Web.UI.UserControl, IWebPart
{
private string _CatalogImageUrl; public string CatalogIconImageUrl
{
get
{
return _CatalogImageUrl;
}
set
{
_CatalogImageUrl = value;
}
}
private string _Description; public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
}
}
public string Subtitle
{
get { return "Internal Customer List"; }
}
private string _TitleImage; public string TitleIconImageUrl
{
get
{
if (_TitleImage == null)
return "CustomersSmall.jpg";
else
return _TitleImage;
}
set
{
_TitleImage = value;
}
}
private string _TitleUrl; public string TitleUrl
{
get
1060 CHAPTER 31 ■ PORTALS WITH WEB PART PAGES
{
return _TitleUrl;
}
set
{
_TitleUrl = value;
}
}
public string Title
{
get
{
if (ViewState["Title"] == null) return string.Empty;
else
return (string)ViewState["Title"];
}
set
{
ViewState["Title"] = value;
}
}
}
When implementing the IWebPart interface, you should think about which property values you want to put into view state and which values are sufficient as private members. Basically, for saving bytes sent across the wire with the page, you should add as little information as possible to the view state. You should use view state only for information that can be edited by the user while browsing and that you don’t want to lose between page postbacks. In the previous example, you used private members for every property of the web part but not for the title property because it might change while browsing (for example, if you want to display the current page of the GridView in the title bar as well). When implementing this interface, the information (which is set from outside) is automatically passed in by the GenericWebPart to your control’s implementation. Consider the following code in your Default.aspx page:
protected void MyCustomers_Load(object sender, EventArgs e)
{
// Some of the properties are set; others like the TitleImageUrl are not! GenericWebPart part = (GenericWebPart)MyCustomers.Parent;
part.Title = "Customers";
part.TitleUrl = "http://www.apress.com";
part.Description = "Displays all customers in the database!";
}
When someone sets the WebPart’s title this way from outside, the GenericWebPart class passes the value to the interface implementation of the Title property so that you can handle the information. On the other hand, if someone queries information such as the Title or TitleUrl, the GenericWebPart retrieves the information from your control by calling the appropriate property in your IWebPart implementation. This way your control can return default values even for properties that have not been explicitly set. Your implementation of the TitleIconImageUrl is doing this. To reiterate, here is the fragment of the previous IWebPart implementation:
1062 CHAPTER 31 ■ PORTALS WITH WEB PART PAGES
Developing Advanced Web Parts
Implementing WebParts through user controls is a fairly easy way to create web parts. But user controls have some disadvantages as well:
Restricted reusability: You cannot add them dynamically to web part pages of other web applications without manually copying the .ascx file to the directories of the other web application. Manually implemented WebParts can be encapsulated in separate assembly DLLs and therefore can be reused in multiple web applications by referencing them through Add References or by copying the DLL into the target web application’s Bin directory.
Restricted personalization: Personalization with user controls is restricted to common properties such as title, title URL, and so on. You cannot have custom properties in the user control that are stored in the personalization store. Only classes that inherit from WebPart can have this sort of functionality.
Better control over rendering and behavior: When using custom server controls, you have better control over the rendering process and can generate user interfaces more dynamically.
Therefore, sometimes implementing advanced web parts as server controls inherited from System.Web.UI.WebControls.WebParts.WebPart is useful. With the basic know-how for creating custom ASP.NET server controls, you are definitely ready to create this sort of web part. All you have to keep in mind when creating a custom web part this way is that ASP.NET pages and ASP.NET controls are processed by the runtime (which determines the order of control and page events and what to do in each of these events). This makes it much easier because you always have the steps for the implementation in mind. For more information about creating custom server controls, refer to Chapter 27.
The steps for creating a custom web part are as follows. (These steps will be familiar to you if you keep the ASP.NET page and control life cycle from Chapter 27 in mind.)
1.Inherit from WebPart: First you have to create a simple class that inherits from System.Web.UI.WebControls.WebParts.WebPart.
2.Add custom properties: Next, add custom properties of your web part and specify through attributes which of those properties can be edited by the user and which of these properties are stored on a per-user or shared basis in the personalization store.
3.Write initialization and loading code: Override any initialization procedure you need. Typically you will override the OnInit method and the CreateChildControls method if you want to create a composite control/WebPart. In most cases, you should create composite controls, because that saves you from rendering HTML code manually. During the initialization phase, you can also load data from databases; in the loading phase (catching the Load event or overriding the OnLoad method), you can initialize other properties of the WebPart (or server control).
4.Catch events of child controls: After the loading phase has been completed, controls will raise their events. Next you can add the event handlers for your child controls to your custom web part.
5.Prerender: Before the rendering phase starts, you should perform the last tasks, such as setting the properties of your controls and actually building the control structure based on data sources they are bound to (for example, calling the DataBind method if you don’t use the new DataSources programming model).
6.Render the HTML: Finally, you have to write code to render your WebPart. This time you don’t override the RenderControl method (as is the case for server controls). You have to override the RenderContents method that is called from the base class in between rendering the border, title bar, and title menu with the appropriate verbs.
CHAPTER 31 ■ PORTALS WITH WEB PART PAGES |
1063 |
Keeping these steps in mind, creating a custom web part is easy (although it’s not as easy as creating web parts based on user controls). Let’s create a simple WebPart using this technique. The WebPart allows customers to add notes to the CustomerNotes table presented in Figure 31-4.
Before You Start: Create Typed DataSets
Before you dig into the details of developing the web part, you have to add special components for easily accessing the data stored in the database. (You also need these components to complete the code samples shown in this chapter.)
In the web parts that you will develop in this chapter, you need to access data from the Customers table and the CustomerNotes table shown in Figure 31-5. For both tables you need to add a typed DataSet (you can find more information about DataSets in Chapter 7 and Chapter 8) to your web application project, as shown in Figure 31-10.
Figure 31-10. The typed DataSets necessary for the solution
Both typed DataSets create the DataSet class and typed table adapters that you will use to develop the remaining parts of the web application in this chapter. In general, you should always create the business layer and data access layer before you start creating the actual user interface components—and Web Parts are definitely user interface components. Although this step dramatically simplifies the process, it demonstrates that you should always create the business layer and/or data access layer before you start with the actual Web Part implementation. Of course, components in the business layer and data access layer are reusable across different applications just as these two typed DataSets are.
1064 CHAPTER 31 ■ PORTALS WITH WEB PART PAGES
The Custom WebPart’s Skeleton
First, you have to create a custom class that inherits from WebPart. Also, you need to include the System.Web.UI.WebControls.WebParts namespace so you have easy access to the web part framework classes.
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
namespace APress.WebParts.Samples
{
public class CustomerNotesPart : WebPart
{
public CustomerNotesPart ()
{
}
}
}
Next, add some properties to your web part. For every property procedure in your class, you can specify whether the property is personalizable on a per-user or on a shared basis as well as whether the property is accessible to users. For example, in your CustomerNotesPart, you can include a property that specifies the default customer for which you want to display the notes, as follows:
private string _Customer = string.Empty;
[WebBrowsable(true)]
[Personalizable(PersonalizationScope.User)] public string Customer
{
get
{
return _Customer;
}
set
{
_Customer = value;
}
}
The WebBrowsable attribute specifies that the property is visible to end users, and the Personalizable attribute specifies that the personalization scope for the property is on a per-user basis.
Initializing the WebPart
To write the initialization code, you can optionally create child controls; you do this just as you would create a composite web part. You can render the web part on your own if you don’t want to use prebuilt controls in the RenderContents method; however, using composite controls makes life much easier, because you don’t have to worry about the HTML details. For creating controls, you have to override the CreateChildControls method as follows. Don’t forget to keep members for every control you are going to create in your WebPart class.
1066 CHAPTER 31 ■ PORTALS WITH WEB PART PAGES
But when overriding the OnLoad method, for example, don’t forget to call base.Onload() so that the base class’s loading functionality is executed as well. Therefore, it makes sense to set up event handlers once and catch the events of your custom control so that you can’t forget this, as follows:
public CustomerNotesPart()
{
this.Init += new EventHandler(CustomerNotesPart_Init); this.Load += new EventHandler(CustomerNotesPart_Load); this.PreRender += new EventHandler(CustomerNotesPart_PreRender);
}
void CustomerNotesPart_Load(object sender, EventArgs e)
{
// Initialize other properties ...
}
void CustomerNotesPart_Init(object sender, EventArgs e)
{
// Load data from the database...
}
You will use the PreRender event later.
Now you can write functionality for loading the data from the database. Let’s assume that you have already created a typed DataSet for your CustomerNotes table. You can create a helper method for binding the previously created GridView to the data from the database and then call this method in the Load event as follows. For simplicity the method binds the information directly to the GridView and doesn’t use caching for optimizing data access, because you should concentrate on WebPart creation now.
void BindGrid()
{
EnsureChildControls();
CustomerNotesTableAdapter adapter = new CustomerNotesTableAdapter();
if (Customer.Equals(string.Empty)) CustomerNotesGrid.DataSource = adapter.GetDataAll();
else
CustomerNotesGrid.DataSource = adapter.GetDataByCustomer(Customer);
}
void CustomerNotesPart_Load(object sender, EventArgs e)
{
// Initialize Web Part Properties this.Title = "Customer Notes"; this.TitleIconImageUrl = "NotesImage.jpg";
}
void CustomerNotesPart_Init(object sender, EventArgs e)
{
// Don't try to load data in Design mode if (!this.DesignMode)
{
BindGrid();
}
}
CHAPTER 31 ■ PORTALS WITH WEB PART PAGES |
1067 |
Remember the call of EnsureChildControls: as you don’t know when ASP.NET creates the controls (because it creates them as they are needed), you need to make sure controls are available from within this method by calling EnsureChildControls. (You can find more information about this in Chapter 27.)
Now you have loaded the data into the grid. During the next phase of the life cycle, events are processed by the ASP.NET runtime. Your custom WebPart has to catch the event for the previously added InsertNewNote button that submits a new note to the database and the CustomerNotesGrid that changes the page, as follows:
void InsertNewNote_Click(object sender, EventArgs e)
{
CustomerNotesTableAdapter adapter =
new CustomerNotesTableAdapter();
adapter.Insert(Customer, DateTime.Now, NewNoteText.Text);
// Refresh the Grid with the new row as well BindGrid();
}
void CustomerNotesGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
CustomerNotesGrid.PageIndex = e.NewPageIndex;
}
Finally, you have to load the data into the GridView in one more place in your code. As soon as someone changes the value for the Customer property, you want your WebPart to display information associated with a single customer. Therefore, you have to modify the property’s code as follows:
[WebBrowsable(true)]
[Personalizable(PersonalizationScope.User)] public string Customer
{
get
{
return _Customer;
}
set
{
_Customer = value;
// Don't try to load data in Design mode if (!this.DesignMode)
{
EnsureChildControls(); CustomerNotesGrid.PageIndex = 0; CustomerNotesGrid.SelectedIndex = -1; BindGrid();
}
}
}
You should reset the page index in case the new data displayed will not fill as many pages as the previous data source filled.
