Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

1242 CHAPTER 32 ASP.NET WEB CONTROLS, THEMES, AND MASTER PAGES

Now be aware that you can only assign a theme programmatically during specific phases of your page’s life cycle. Typically, this will be done within the Page_PreInit event. This being said, update your code file as follows:

partial class _Default : System.Web.UI.Page

{

protected void btnNoTheme_Click(object sender, System.EventArgs e)

{

//Empty strings result in no theme being applied.

Session["UserTheme"] = "";

//Triggers the PreInit event again.

Server.Transfer(Request.FilePath);

}

protected void btnGreenTheme_Click(object sender, System.EventArgs e)

{

Session["UserTheme"] = "BasicGreen";

// Triggers the PreInit event again.

Server.Transfer(Request.FilePath);

}

protected void btnOrangeTheme_Click(object sender, System.EventArgs e)

{

Session["UserTheme"] = "CrazyOrange";

// Triggers the PreInit event again.

Server.Transfer(Request.FilePath);

}

protected void Page_PreInit(object sender, System.EventArgs e)

{

try

{

Theme = Session["UserTheme"].ToString();

}

catch

{

Theme = "";

}

}

}

Notice that we are storing the selected theme within a session variable (see Chapter 33 for details) named UserTheme, which is formally assigned within the Page_PreInit() event handler. Also note that when the user clicks a given Button, we programmatically force the PreInit event to fire by calling Server.Transfer() and requesting the current page once again. If you were to run this page, you would now find that you can establish your theme via various Button clicks.

Source Code The FunWithThemes project is included under the Chapter 32 subdirectory.

CHAPTER 32 ASP.NET WEB CONTROLS, THEMES, AND MASTER PAGES

1243

Positioning Controls Using HTML Tables

If you are new to web development, you may have quickly noticed that positioning controls on a designer surface is far from intuitive. For example, unlike with Windows Forms, you cannot (by default) drag a UI element from the Toolbox and position it exactly where you want to (which as you might agree is quite frustrating).

Earlier versions of ASP.NET provided two modes of positioning (GridLayout and FlowLayout) that could be set via the pageLayout attribute of the DOCUMENT. When set to GridLayout, absolute positioning was possible using DHTML. This, however, made ASP.NET 1.x web pages limited to browsers that supported dynamic HTML. FlowLayout (the current default mode for ASP.NET) does not provide for absolute position . . . which can be frustrating to develop with; however, it does ensure every browser can correctly display the web content.

Strictly speaking, ASP.NET does still allow developers to define controls (manually) using GridLayout semantics. However, the designers will complain, as the necessary infrastructure is not considered valid within the XHTML specification. For example, consider the following *.aspx file, which makes use of the style attribute to provide absolute position to a Button type using the style attribute of the Button type:

<body MS_POSITIONING="GridLayout">

<form id="Form2" method="post" runat="server">

<asp:Button id="Button1" runat="server" Text="Button" style="Z-INDEX: 101; LEFT: 106px; POSITION: absolute; TOP: 79px">

</asp:Button>

<asp:TextBox id="TextBox1" runat="server"> </asp:TextBox>

</form>

</body>

Rather than making use of non–XHTML-compliant code (and risk the chance of not working within every browser), many web developers place widgets within HTML tables. The HTML table is not literally visible in the browser; however, at design time, controls may be placed within the cells to provide a level of absolute positioning.

Better yet, Visual Studio 2008 allows you to edit and manipulate these cells visually in a manner similar to an Excel spreadsheet. For example, the Tab key moves you between each cell, and selecting multiple cells allows you to merge/resize them via the context menu. Furthermore, each cell can be customized with various styles via the Properties window. By way of a quick example, consider the designer snapshot of an HTML table control on an arbitrary *.aspx file shown in Figure 32-26.

Once you have configured the cells of your table (which typically include other nested tables), you are then able to arrange the ASP.NET web controls in a manner of your choice. The benefit is that as the user resizes the web browser, the controls retain their relative positioning.

Note The remaining examples of this section of the book do not require you to design pages using HTML tables; however, you should be aware of their usefulness in web development.

1244 CHAPTER 32 ASP.NET WEB CONTROLS, THEMES, AND MASTER PAGES

Figure 32-26. Visual Studio 2008 provides excellent HTML table configuration support.

Summary

This chapter examined how to make use of various ASP.NET web controls. We began by examining the role of the Control and WebControl base classes, and you came to learn how to dynamically interact with a panel’s internal controls collection. Along the way, you were exposed to the new site navigation model (*.sitemap files and the SiteMapDataSource component), the new data binding engine (via the SqlDataSource component and the new GridView type), and various validation controls.

The latter half of this chapter examined the role of master pages and themes. Recall that master pages can be used to define a common frame for a set of pages on your site. Also recall that the *.master file defines any number of “content placeholders” to which content pages plug in their custom UI content. Finally, as you were shown, the ASP.NET theme engine allows you to declaratively or programmatically apply a common UI look and feel to your widgets on the web server.

C H A P T E R 3 3

ASP.NET State Management

Techniques

The previous two chapters concentrated on the composition and behavior of ASP.NET pages and the web controls they contain. This chapter builds on that information by examining the role of the Global.asax file and the underlying HttpApplication type. As you will see, the functionality of HttpApplication allows you to intercept numerous events that enable you to treat your web applications as a cohesive unit, rather than a set of stand-alone *.aspx files.

In addition to investigating the HttpApplication type, this chapter also addresses the allimportant topic of state management. Here you will learn the role of view state, session and application variables (including the application cache), cookie data, and the ASP.NET Profile API.

The Issue of State

At the beginning of the Chapter 31, I pointed out that HTTP on the Web results in a stateless wire protocol. This very fact makes web development extremely different from the process of building an executable assembly. For example, when you are building a Windows Forms application, you can rest assured that any member variables defined in the Form-derived class will typically exist in memory until the user explicitly shuts down the executable:

public partial class MainWindow : System.Windows.Forms.Form

{

// State data!

private string userFavoriteCar = "Yugo";

}

In the world of the World Wide Web, however, you are not afforded the same luxurious assumption. To prove the point, create a new ASP.NET website named SimpleStateExample. Within the code-behind file of your initial *.aspx file, define a page-level string variable named userFavoriteCar:

public partial class _Default : System.Web.UI.Page

{

// State data?

private string userFavoriteCar = "Yugo";

protected void Page_Load(object sender, EventArgs e)

{

}

}

1245

1246 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

Next, construct the web UI as shown in Figure 33-1.

Figure 33-1. The UI for the simple state page

The server-side Click event handler for the Set button (named btnSetCar) will allow the user to assign the string member variable to the value within the TextBox (named txtFavCar):

protected void btnSetCar_Click(object sender, EventArgs e)

{

// Store fave car in member variable. userFavoriteCar = txtFavCar.Text;

}

while the Click event handler for the Get button (btnGetCar) will display the current value of the member variable within the page’s Label widget (lblFavCar):

protected void btnGetCar_Click(object sender, EventArgs e)

{

// Show value of member variable. lblFavCar.Text = userFavoriteCar;

}

Now, if you were building a Windows Forms application, you would be right to assume that once the user sets the initial value, it would be remembered throughout the life of the desktop application. Sadly, when you run this web application, you will find that each time you post back to the web server (by clicking either button), the value of the userFavoriteCar string variable is set back to the initial value of “Yugo”; therefore, the Label’s text is continuously fixed.

Again, given that HTTP has no clue how to automatically remember data once the HTTP response has been sent, it stands to reason that the Page object is destroyed almost instantly. Therefore, when the client posts back to the *.aspx file, a new Page object is constructed that will reset any page-level member variables. This is clearly a major dilemma. Imagine how useless online shopping would be if every time you posted back to the web server, any and all information you previously entered (such as the items you wished to purchase) were discarded. When you wish to remember information regarding the users who are logged on to your site, you need to make use of various state management techniques.

Note This issue is in no way limited to ASP.NET. Java servlets, CGI applications, classic ASP, and PHP applications all must contend with the thorny issue of state management.

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1247

To remember the value of the userFavoriteCar string type between postbacks, you are required to store the value of this string type within a session variable. You will examine the exact details of session state in the pages that follow. For the sake of completion, however, here are the necessary updates for the current page (note that you are no longer using the private string member variable, therefore feel free to comment out or remove the definition altogether):

public partial class _Default : System.Web.UI.Page

{

//State data?

//private string userFavoriteCar = "Yugo";

protected void Page_Load(object sender, EventArgs e)

{

}

protected void btnSetCar_Click(object sender, EventArgs e)

{

// Store value to be remembered in session variable.

Session["UserFavCar"] = txtFavCar.Text;

}

protected void btnGetCar_Click(object sender, EventArgs e)

{

// Get session variable value.

lblFavCar.Text = (string)Session["UserFavCar"];

}

}

If you now run the application, the value of your favorite automobile will be preserved across postbacks, thanks to the HttpSessionState object manipulated indirectly by the inherited Session property.

Source Code The SimpleStateExample project is included under the Chapter 33 subdirectory.

ASP.NET State Management Techniques

ASP.NET provides several mechanisms that you can use to maintain stateful information in your web applications. Specifically, you have the following options:

Make use of ASP.NET view state.

Make use of ASP.NET control state.

Define application-level variables.

Make use of the cache object.

Define session-level variables.

Define cookie data.

The one thing these approaches have in common is that they each demand that a given user is in session and that the web application is loaded into memory. As soon as a user logs off (or times out) from your site (or your website is shut down), your site is once again stateless. If you wish to

1248 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

persist user data in a permanent manner, ASP.NET provides an out-of-the-box Profile API. We’ll examine the details of each approach in turn, beginning with the topic of ASP.NET view state.

Understanding the Role of ASP.NET View State

The term view state has been thrown out a few times here and in the previous two chapters without a formal definition, so let’s demystify this term once and for all. Under classic (COM-based) ASP, web developers were required to manually repopulate the values of the incoming form widgets during the process of constructing the outgoing HTTP response. For example, if the incoming HTTP request contained five text boxes with specific values, the *.asp file required script code to extract the current values (via the Form or QueryString collections of the Request object) and manually place them back into the HTTP response stream (needless to say, this was a drag). If the developer failed to do so, the caller was presented with a set of five empty text boxes!

Under ASP.NET, we are no longer required to manually scrape out and repopulate the values contained within the HTML widgets because the ASP.NET runtime will automatically embed a hidden form field (named __VIEWSTATE), which will flow between the browser and a specific page. The data assigned to this field is a Base64-encoded string that contains a set of name/value pairs that represent the values of each GUI widget on the page at hand.

The System.Web.UI.Page base class’s Init event handler is the entity in charge of reading the incoming values found within the __VIEWSTATE field to populate the appropriate member variables in the derived class (which is why it is risky at best to access the state of a web widget within the scope of a page’s Init event handler).

Also, just before the outgoing response is emitted back to the requesting browser, the __VIEWSTATE data is used to repopulate the form’s widgets, to ensure that the current values of the HTML widgets appear as they did prior to the previous postback.

Clearly, the best thing about this aspect of ASP.NET is that it just happens without any work on your part. Of course, you are always able to interact with, alter, or disable this default functionality if you so choose. To understand how to do this, let’s see a concrete view state example.

Demonstrating View State

First, create a new ASP.NET web application called ViewStateApp. On your initial *.aspx page, add a single ASP.NET ListBox web control (named myListBox) and a single Button type (named btnPostback). Handle the Click event for the Button to provide a way for the user to post back to the web server:

public partial class _Default : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

}

protected void btnPostback_Click(object sender, EventArgs e)

{

// No-op. This is just here to allow a postback.

}

}

Now, using the Visual Studio 2008 Properties window, access the Items property and add four ListItems to the ListBox using the associated dialog box. The resulting markup looks like this:

<asp:ListBox ID="myListBox" runat="server"> <asp:ListItem>Item One</asp:ListItem> <asp:ListItem>Item Two</asp:ListItem>

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1249

<asp:ListItem>Item Three</asp:ListItem> <asp:ListItem>Item Four</asp:ListItem>

</asp:ListBox>

Note that you are hard-coding the items in the ListBox directly within the *.aspx file. As you already know, all <asp:> definitions found within an HTML form will automatically render back their HTML representation before the final HTTP response (provided they have the runat="server" attribute).

The <%@Page%> directive has an optional attribute called EnableViewState that by default is set to true. To disable this behavior, simply update the <%@Page%> directive as follows:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" EnableViewState ="false" %>

So, what exactly does it mean to disable view state? The answer is, it depends. Given the previous definition of the term, you would think that if you disable view state for an *.aspx file, the values within your ListBox would not be remembered between postbacks to the web server. However, if you were to run this application as is, you might be surprised to find that the information in the ListBox is retained regardless of how many times you post back to the page.

In fact, if you examine the source HTML returned to the browser (by right-clicking the page within the browser and selecting View Source), you may be further surprised to see that the hidden

__VIEWSTATE field is still present:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTM4MTM2MDM4NGRkqGC6gjEV25JnddkJiRmoIc10SIA=" />

The reason the view state string is still visible is the fact that the *.aspx file has explicitly defined the ListBox items within the scope of the HTML <form> tags. Thus, the ListBox items will be autogenerated each time the web server responds to the client.

However, assume that your ListBox is dynamically populated within the code-behind file rather than within the HTML <form> definition. First, remove the <asp:ListItem> declarations from the current *.aspx file:

<asp:ListBox ID="myListBox" runat="server"> </asp:ListBox>

Next, fill the list items within the Load event handler within your code-behind file:

protected void Page_Load(object sender, EventArgs e)

{

if (!IsPostBack)

{

// Fill ListBox dynamically! myListBox.Items.Add("Item One"); myListBox.Items.Add("Item Two"); myListBox.Items.Add("Item Three"); myListBox.Items.Add("Item Four");

}

}

If you post to this updated page, you will find that the first time the browser requests the page, the values in the ListBox are present and accounted for. However, on postback, the ListBox is suddenly empty. The first rule of ASP.NET view state is that its effect is only realized when you have widgets whose values are dynamically generated through code. If you hard-code values within the *.aspx file’s <form> tags, the state of these items is always remembered across postbacks (even when you set EnableViewState to false for a given page).

1250 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

Furthermore, view state is most useful when you have a dynamically populated web widget that always needs to be repopulated for each and every postback (such as an ASP.NET GridView, which is always filled using a database hit). If you did not disable view state for pages that contain such widgets, the entire state of the grid is represented within the hidden __VIEWSTATE field. Given that complex pages may contain numerous ASP.NET web controls, you can imagine how large this string would become. As the payload of the HTTP request/response cycle could become quite heavy, this may become a problem for the dial-up web surfers of the world. In cases such as these, you may find faster throughput if you disable view state for the page.

If the idea of disabling view state for the entire *.aspx file seems a bit too aggressive, do know that every descendent of the System.Web.UI.Control base class inherits the EnableViewState property, which makes it very simple to disable view state on a control-by-control basis:

<asp:GridView id="myHugeDynamicallyFilledGridOfData" runat="server" EnableViewState="false">

</asp:GridView>

Note ASP.NET pages reserve a small part of the __VIEWSTATE string for internal use. Given this, you will find that the __VIEWSTATE field will still appear in the client-side source even when the entire page (and all the controls) have disabled view state.

Adding Custom View State Data

In addition to the EnableViewState property, the System.Web.UI.Control base class provides an inherited property named ViewState. Under the hood, this property provides access to a System. Web.UI.StateBag type, which represents all the data contained within the __VIEWSTATE field. Using the indexer of the StateBag type, you can embed custom information within the hidden __VIEWSTATE form field using a set of name/value pairs. Here’s a simple example:

protected void btnAddToVS_Click(object sender, EventArgs e)

{

ViewState["CustomViewStateItem"] = "Some user data"; lblVSValue.Text = (string)ViewState["CustomViewStateItem"];

}

Because the System.Web.UI.StateBag type has been designed to operate on any type-derived System.Object, when you wish to access the value of a given key, you should explicitly cast it into the correct underlying data type (in this case, a System.String). Be aware, however, that values placed within the __VIEWSTATE field cannot literally be any object. Specifically, the only valid types are Strings, Integers, Booleans, ArrayLists, Hashtables, or an array of these types.

So, given that *.aspx pages may insert custom bits of information into the __VIEWSTATE string, the next logical question is when you would want to do so. Most of the time, custom view state data is best suited for user-specific preferences. For example, you may establish a point of view state data that specifies how a user wishes to view the UI of a GridView (such as a sort order). View state data is not well suited for full-blown user data, such as items in a shopping cart or cached DataSets. When you need to store this sort of complex information, you are required to work with session or application data. Before we get to that point, you need to understand the role of the Global.asax file.

Source Code The ViewStateApp project is included under the Chapter 33 subdirectory.

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1251

A BRIEF WORD REGARDING CONTROL STATE

Since the release of .NET 2.0, a control’s state data can now be persisted via control state rather than view state. This technique is most helpful if you have written a custom ASP.NET web control that must remember data between round-trips. While the ViewState property can be used for this purpose, if view state is disabled at a page level, the custom control is effectively broken. For this very reason, web controls now support a ControlState property.

Control state works identically to view state; however, it will not be disabled if view state is disabled at the page level. As mentioned, this feature is most useful for those who are developing custom web controls (a topic not covered in this text). Consult the .NET Framework 3.5 SDK documentation for further details.

The Role of the Global.asax File

At this point, an ASP.NET application may seem to be little more than a set of *.aspx files and their respective web controls. While you could build a web application by simply linking a set of related web pages, you will most likely need a way to interact with the web application as a whole. To this end, your ASP.NET web applications may choose to include an optional Global.asax file via the Web Site Add New Item menu option, as shown in Figure 33-2 (notice you are selecting the Global Application Class icon).

Figure 33-2. The Global.asax file

Simply put, Global.asax is just about as close to a traditional double-clickable *.exe that we can get in the world of ASP.NET, meaning this type represents the runtime behavior of the website itself. Once you insert a Global.asax file into a web project, you will notice it is little more than a <script> block containing a set of event handlers: