Documente Academic
Documente Profesional
Documente Cultură
Understanding Page lifecycle is very crucial in order to develop ASP.NET applications. Most beginners tend to get confused while dealing with dynamic controls and face problems like losing values, state etc on postbacks. Since HTTP is stateless, the nature of web programming is inherently different from windows application development, and the Page lifecycle is one of the primary building blocks while learning ASP.NET. The sequence of events, especially while working with MasterPages in ASP.NET 2.0, has become slightly more complex and this article is aims to shed some light on these events by explaining the order and importance of each event.
Background
Whenever the user requests a particular .aspx page in an application, a lot of interesting things happen on the web server where the application is hosted. Understanding this sequence of events will help us to program and respond to events properly and also clear any confusion which generally arises due to the stateless nature of web programming.
Side Note: View state in ASP.NET 2.0 has changed and now comprises of two parts: Control State and View state. Refer this article for details: http://msdn2.microsoft.com/en-us/library/1whwt1k7(VS.80).aspx Below is the sequence of events which fire up sequentially with explanation on the relative importance with respect to web programming in code behind: Important Note: All events except the Init() and Unload() are fired from outermost to the innermost control. For e.g., a user controls init event would fire before the Page_Init() event of its parent Page class.
1. PreInit()
In this Page level event, all controls created during design time are initialized with their default values. For e.g., if you have a TextBox control with Text property = Hello, it would be set by now. We can create dynamic controls here. This event occurs only for the Page class and UserControls/MasterPages do not have this method to override. Sample code where you can override this method and add your custom code:
{ //custom code base.OnPreInit(e); } Note that PreInit() is the only event where we can set themes programmatically. Special Case with MasterPages It is important to note that Master Page is treated like a control in the Content Pages. So if a Page has a Master Page associated with it, then the controls on the page will not be initialized and would be null in this stage. Only after the Init() event starts, you can access these controls directly from the page class. Why? The reason being that all controls placed in the Content Page are within a ContentPlaceholder which is a child control of a MasterPage. Now Master Page is merged and treated like a control in the Content Pages. As I mentioned earlier, all events
except the Init() and Unload() are fired from outermost to the innermost control. So PreInit() in the Page is the first event to fire but User Controls or MasterPage (which is itself a Usercontrol) do not have any PreInit event . Therefore in the Page_PreInit() method, neither the MasterPage nor any user control has been initialized and only the controls inside the Page class are set to their default values. Only after the Page_PreInit() event the Init() events of other controls fire up. See the diagram below showing control hierarchy after the Page_Init() event:
2. OnInit()
In this event, we can read the controls properties (set at design time). We cannot read control values changed by the user because that changed value will get loaded after LoadPostData() event fires. But we can access control values from the forms POST data as:
3. LoadViewState
This will only fire if the Page has posted back (IsPostBack == true). Here the runtime de-serializes the view state data from the hidden form element and loads all controls who have view state enabled.
4. LoadPostBackData
Again, this method will only fire if the Page has posted back. In this event the controls which implement IPostBackDataHandler interface gets loaded by the values from the HTTP POST data. Note that a textbox control does not gets its value from the view state but from the post data in the form in this event. So even if you disable view state for a particular control, it can get its value from the HTTP POST data if it implements IPostBackDataHandler interface. Also, an important point to note is that if we have a DropDownList control and we have dynamically added some items to it, the runtime cannot load those values unless the view state is enabled (even if the control derives from IPostBackDataHandler). The reason being that HTTP Post data has only one value per control, and the entire value collection is not maintained in the PostData but in view state.
5. Page_Load
This is the most popular method and the first one for all beginner developers to put their code. Beginners may also think that this is the first method which fires for a Page class. This can lead to a lot of confusion which makes understanding the Page lifecycle all the more important. Note: If the page has any user control, then it's Load method will fire after the Page class's Load method. The reason as explained earlier is the fact that all method except the Init() are fired from the outermost control to the innermost. So after Page_Load(), load methods of all other controls are fired recursively.
7. PreRender
This event is again recursively fired for each child controls in the Page. If we want to make any changes to control values, this is the last event we have to peform the same.
8. SaveViewState
Here, the ViewState of the controls gets saved in the form's hidden control.
9. Render
In this method all controls are rendered recursively (i.e. Render method of each control is called).
10. Unload
Here you can have the page and controls perform clean-up operations. This event has no relevance besides clean up operations because the Page has already rendered.
Dynamic Controls
Now we have seen the important events in the Page lifecycle, let's focus on how to create and maintain state of dynamically generated controls. Many times we need to generate controls dynamically for specific business use cases. For example, I was managing a famous hotel reservation website project and one of my team members was facing an issue in handling the Reservation screen. There was a TextBox where the user enters the number of rooms, and based on that value, dynamic usercontrols having a room's detailed info were created at runtime. The developer complained that although he was able to generate user controls as runtime in a for loop, but was unable to save their state. When I looked into the code, I noticed that the code to generate controls was written in a Button's Click event handler. Now as we dicussed above, event handlers like Button_Click() fire much later than LoadViewState() and LoadPostData(), where the control values get loaded from the view state and form's Post data. So unless he recreates the controls in the Page_Init() or Pre_Init() methods (which occur before LoadViewState and LoadPostData), the control values modified by the user won't get reloaded next time. Now, when he put the code in the Page_Init() event, he was unable to get the number of rooms entered by the user in the TextBox (which was a static control). The reason being that in Page_Init(), control values are initilized to their design time default values, and do not reflect the user entered values unless they are loaded from the POST data or the view state, a process which occurs later. So the only way to access the user entered value in the control is to get the value from the form's POST data. Here is the code:
base.OnInit(e); }
Note: Thanks to Mike Banavige of ASP.NET forums, I added this section. If you create a dynamic control in the Page_Load event, and add it to a PlaceHolder or Panel (with view state turned on), then this dynamic control will maintain its state even though it was not created in the Page_Init(). Why? The reason is the fact that once a control is added to the control tree of the page,
TrackViewState() method is responsible for tracking the state. This method gets fired
automatically whenever the control is added to the control tree. Due to this reason, any modifications to the control (like adding items etc) should be done *only after* the dynamic control has been added to the control tree of the Page class, else the state would be lost. See the code below:
protected void Page_Load(object sender, EventArgs e) { //create a dynamic dropdown DropDownList d = new DropDownList(); PlaceHolder1.Controls.Add(d); // TrackViewState() gets fired for our dropdown, so state is maintained
This will not work: protected void Page_Load(object sender, EventArgs e) { //create a dynamic dropdown DropDownList d = new DropDownList(); if (!IsPostBack) { d.Items.Add("test1"); d.Items.Add("test2"); } PlaceHolder1.Controls.Add(d); //"test1" and "test2" values are lost }
Summary
I have tried to explain relevant events in the Page lifecycle and their importance with some gotchas. I will keep updating this article with more tips and tricks, besides readers are welcome to point out mistakes and suggest corrections and give feedback! An important thing to remember here is that the entire lifecycle is repeated on every request. The Page class gets re-instantiated and after serving the request gets unloaded. Once we are clear with the sequence of the events, we can structure our code well to suit different use case requirements.
The following table lists the Client-side and server-side options: CLIENT-SIDE OPTIONS View State property Hidden Fields Cookies Query Strings SERVER-SIDE OPTIONS Application state Session state Database support
View State
The ViewState property of an ASP.NET web page enables you to retain page and controlspecific values between round trips. Each web page and the controls on the page have the ViewState property that is inherited from the base Control class. ASP.NET framework uses the ViewState property to automatically save the values of the Web page and each control on the Web page prior to rendering the page. During postback of a Web page to itself, one of the tasks performed by ASP.NET is to restore the values in _VIEWSTATE. The view state is implemented with a hidden form field called _VIEWSTATE, which is automatically created in every Web page. When ASP.NET executes a Web page on a Web server, the values stored in the ViewState property of the page and controls on it are
collected and formatted into a single encoded string. The encoded string is then assigned to the Value attribute of the hidden form field _VIEWSTATE and is sent to the client as a part of the Web page. When page is initialized during postback, ASP.NET Framework parses the ViewState string to restore the property information in the page. The view state of a Web page or a control consists of the cumulative property values of the page or the control. To preserve these values across stateless HTTP requests, Web pages and controls in ASP.NET use an instance of the StateBag class. This class is the primary storage mechanism for HTML and server controls. It stores attribute/value pairs associated with HTML and server controls as strings. Therefore, this class is used to manage the view state of an ASP.NET Web page and the controls added to the page after the OnInit method is executed. When you add values to this class, the values are automatically added to the hidden _VIEWSTATE form variable. The syntax to add a value to the StateBag class is as follows: ViewState["any string"] = "some value"; By default, the Viewstate property of both Web page and the controls on the Web page is enabled. Perform the following steps to determine the effect of enabling and disabling the ViewState property in an ASP.NET web application: Create a new Web application. Add a label and a button control to Webform1.aspx. The label control and the button control should have the default name, Label1 and Button1, repectively. Ensure that the EnableViewState property of the Label and Button control are set to true. In addition, ensure that the EnableViewState property to the Page is set to true. Add the following lines of code in the load event of the page and execute the application. if(!IsPostBack) { Label1.Text="Hello"; } When the page is first loaded in the memory of the client computer, the message Hello is assigned to the Text property of the label control. However, if you click the button and reload the page, the label continues to display the message. It is because the Text property of the Label control, Label1 is automatically preserved through the view state property of the control. Now if you set the ViewState property of the label control to false and click the button, the displayed text would not be "hello" but "Label", the default text. Advantages of using ViewState Control The values in view state are stored in a standard HTML format as part of a Web page. Therefore, no Web server resource is required to maintain it. The view state can be easily implemented in the ASP.NET Web page. You just need to set the EnableViewState property of a Web page and server controls.
The values in view state are hashed, compressed and encoded for Unicode implementations. Therefore, values in view state are more secured than values stored in hidden fields. Limitations in using ViewState Storing large values can cause a page to slow down when user display it or post it because view state is stored in the page. View State is stored in a hidden field in a page. Although view state stores data in a hashed format. It can be tampered with. The information in the hidden field can be seen if the page output source is viewed directly, creating a potential security issue.
Hidden Fields
In ASP.NET, you can use the HTML-standard hidden fields in a Web form to store pagespecific information. A hidden field does not render in a Web browser. However, you can set the properties of the hidden field. When a page is submitted to the server, the content of a hidden field is sent in the HTTPForm collection along with the values or other controls. A hidden field stores a single variable in its value property and must be explicitly added to the page. In ASP.NET, the HtmlInputHidden control provides the hidden field functionality. Perform the following steps to implement an HtmlInputHidden control to store page-specific information: Create a new Web application and add a Text box and a button control to the Webform1.aspx. Add a hidden control onto the form form the HTML tab in the toolbox. Right click the hidden control and select the Run As Server Control option form the short-cut menu. Add a label control to the form. Add the following lines of code in the load event of the page: if (Page.IsPostBack) { Label1.Text="Hidden Value: "+ Hidden1.Value; } Add the following line of code in the Click event of the Button control. Hidden1.Value=TextBox1.Text; When the application is executed and you click the button after entering any text in the text box, the entered text id assigned to the Value property of the HtmlInputHidden control. During the round trip of the Web page, the value remains stored in the HtmlInputHidden control. At the time of loading the page, the value stored in Hidden1 is assigned to the Text property of the Label1. Interestingly, when a user clicks the button without specifying the value in the text box, label1 displays only the text Hidden Value without any additional text. This is because the value of the text box is always assigned to the hidden variable. In
addition, when the page loads for the first time, Page.IsPostBack will return false. Therefore, the text Hidden Value will not be displayed. Note: If you use hidden fields, you must submmit your pages to the server using the HTTP POST method instead of requesting the page using the page URL. You cannot take advantage of hidden fields if a page is processed in response to a link or the HTTP GET method. Advantages of using Hidden Form fields The hidden field can store page-specific information without accessing any Web server or Web browser resource. The hidden field can be easily implemented in an ASP.NET web form page. Limitations in using Hidden form fields You can view the information stored in the hidden field by accessing the source of the Web page. Therefore, the values stored in hidden form fields are not very secure. The hidden field does not support more than a single value field to store information. The hidden fields are stored in a page. Therefore, storing large values in hidden form fields can slow down the processing of the page.
Cookies
A cookie is used to store small piece of information on client machine. A cookie contains page-specific information that a Web server sends to a client along with page output. Cookies are used for sending page-specific information because HTTP is a stateless protocol and cannot indicate whether page requests are coming from the same or different clients. You can use cookies to keep track of each individual user who access a Web page across an HTTP connection. Cookies are saved on the Client computer. Cookies can either be temporary or persistent. Temporary cookies, also known as session cookies, exist in the memory space of a browser. When the browser is closed, all session cookies added to the browser are lost. A persistent cookie is saved as a text file in the file system of the client computer. A Web browser can access a cookie from the HttpCookieCollection by using the Request object. If a cookie is accessed using the Request built-in object, the cookie is a read only file. The cookie is stored on the Web browser and not on the Web server. However, if you want to modify a cookie, you need to use the Response built in object. Perform the following steps to create a persistent cookie on a client computer and read the contents of the cookie: Create a new web application and add a button control to the form. Add the following lines of code in the click event of the button control. HttpCookie mycookie=new HttpCookie ("Cookiename","hello"); mycookie.Expires =System.Convert .ToDateTime ("12/12/2007"); Response.Cookies.Add (mycookie);
HttpCookie myvar; myvar=Request.Cookies .Get ("Cookiename"); Response.Write ("Cookie :" + myvar.Name + "<br>"); Response.Write ("Expires : " + myvar.Expires + "<br>"); Now run the application. When you execute the application, only a button is displayed in the Web browser. After you click the button in the web form, a cookie with the name Cookiename is created in the Cookies folder on the client computer. After the cookie is created on the client computer, the details of the cookie, such as the name and the expiry date of the cookie are displayed in the client browser. Advantages of using Cookies A cookie is stored on the client computer and can be read by the server after a request for a page is posted. Therefore, no server resource is involved in maintaining the cookie. A cookie is a text-based data structure that contains key value pairs. Therefore, it is easy to create and manipulate cookies. A cookie can either expire when the browser session ends or exists indefinitely on the client computer, subject to the expiration rules on the client. Limitations in using Cookies Cookies that are stored on client computers have a limited size. Most browsers allow cookies to have up to 4096 bytes in size. Therefore, you cannot store a large amount of data in a cookie. Users can disable cookies to prevent them from being stored on the hard disk of their computer. If a user denies permission for cookies, an ASP.NET Web application cannot store cookies on the client computer. Users can tamper with cookies because cookies are stored on the client computer. Note: The above stated limitations of cookies are overcome in ASP.NET by means of cookie-less property. If you set the cookie-less property to TRUE, ASP.NET will embed the session id, normally stored in the cookie, into the URL that is sent back to the client. When the client makes a request using the URL containing the session id, ASP.NET is able to extract the session id and map the request to the appropriate session data.
Query String
If you need to submit information back to a Web page or another page by using a URL, you can use a query string. The query string is part of the request that appears after the Question mark (?) character in the URL. A query string provides a simple way to pass information from one page to another. For example, in a logon screen, the user name can be passed to the next page in the application by using query strings. Perform the following steps to pass a user name form one page to another page using query string:
1. In an ASP.NET Web application add one TextBox control and specify the ID property as txtusername. 2. Add one label control to indicate the type of value the user needs to enter in the TextBox control. Specify the Text property of the Label control as Enter username. 3. Add another label control to display an error message when the user does not enter any value in the TextBox control. Specify the ID of the label as lblmsg. Delete the default text property of the control. 4. Add a Web form to the project by using the Add Web form option from the project menu. Name it a mywebform. 5. Add a button control to WEbForm1.aspx and set the text property value as Submit. 6. Add the following lines of code in the click event of Button1. if (txtusername.Text == "") { lblmsg.Text = "Enter your name in the text box"; } else { Response.Redirect ("mywebform.aspx?=name="+ System.Web.HttpUtility.UrlEncode (txtusername.text)); //The HttpUtility class provides methods for encoding and decoding URLs when processing Web requests. } In the above code, the Redirect method of the Response object is used to redirect the execution of the Web application form WebForrm1.aspx Web page to mywebform.aspx. The UrlEncode method of the HttUtility class is used to pass the string entered in the txtusrname text box from WebForm1.aspx to mywebform.aspx in an encoded format. Therefore when user enters a username in the textbox control, the value that the user has entered is passed to the mywebform.aspx web page as part of the URL. If a user enters 'Richard' as the username and clicks the Submit button, mywebform.aspx is displayed with a URL as shown in the following figure (notice the shaded blue portion):
To ensure that query string values are available during page processing, you must submit the page by using the HTTP Get method. You can pass two or more name/value pairs using a query string. You can do this by separating each pair with an ampersand (&). In this case the ned of the URL might look like this:
?name=Richard&ID=A8135 Advantages of Query String A query string is contained in the HTTP request for a specific URL. Therefore, no server resource is involved to process a query string. Many Web browsers support passing values in a query string. Therefore, if you create a Web application that passes information from one page to another using a query string. Limitations in using Query String The information in a query string is directly visible to the user in the browser window. Therefore, you need to encrypt this information to ensure that any confidential information is not exposed to strangers. This causes an additional overhead to Web application development. There is a limit to the amount of information that you can pass from one page to another using query strings because most browsers support up to 255 characters of URL.
Application State
ASP.NET provides Application state as a means of storing application wide-specific information such as objects and variables. The information in the application state is stored in a key-value pair and is used to maintain data consistency between server round trips and between pages. Application state is created when each browser request is made for a specific URL. After an application state is created, the application-specific information is stored in it. All information stored in the application state is shared among all the pages of the Web application by using the HttpApplicationState class. The HttpApplictaionState class is accessed using the Application property of the HttpContext object. Variables and objects added to the application state are global to an ASP.NET application. Syntax to create a variable and store it in the application state: Application["myvar"] = "Hello"; After the application in which you declared myvar is executed, any page contained in the application can retrieve the value of the myvar. To read the value of the myvar, you need to use the following statement: Response.Write(Application["myvar"]);
You can also add complex objects, such as Collection and Dataset, in application state. For example, you can add a dataset to an application state by using the following statements: DataSet ds = new DataSet(); Application["DataSet"] = ds; To remove the application variable myvar from the application state, you need to use the following statement: Application.Remove(["myvar"]); or to remove all variable for application state wrote the following line of code: Application.RemoveAll(); It is important to note that after an object is added to an application state, it remains in the application state until the application is shut down, the Global.asax file is modified, or the item is explicitly removed from the application state. Since these variables are global to an application, it is important to consider the following issues while storing any value in an application state variable: The memory occupied by variables stored in an application state is not released until the value is either removed or replaced. Therefore, the number of variables and objects in an application state should be minimum. Otherwise, it will be an overhead on the Web server and the server response will be slow. Multiple pages within an application can access values stored in an application state simultaneously. Therefore, explicit synchronization methods need to be used to avoid deadlocks and access violations. The ASP.NET application supports events. Two important events associated with ASP.NET application are discussed in the following table: EVENT DESCRIPTION Application_Start This event is triggered when an application starts. If you want a code to be executed as the application starts, you should add it to the Application_Start event. This event is triggered only when the application starts and is not triggered again until the IIS is stopped, the Global.asax file is modified, or the application is unloaded. Application_End This event is triggered when an application ends
Application State and Synchronization Multiple pages within an ASP.NET Web application can simultaneously access values, stored in an application state. This can result in conflicts and deadlocks. For example you can add a variable named PageCounter in the application state to keep track of the number of times a page has been requested. If two users access a Web page simultaneously, there will be an attempt to update the value of the variable PageCounter simultaneously. This will lead to a
problem. To avoid such situations, the HttpApplicationState class provides two methods, LOCK ( ) and UNLOCK ( ). These methods only allow one thread at a time to access applications state variables and objects. Note: Each browser request for a Web page initiates a new thread on the Web server. Calling the Lock ( ) method on an Application object causes ASP.NET to block attempts by the code running on other worker threads to access anything in an application state. These threads are unblocked only when the thread that called the Lock ( ) method calls the corresponding Unlock ( ) method on the Application object. The following example illustrates the use of the Lock ( ) method and the Unlock ( ) method: Create a new ASP.NET Web application. Add the following lines of code in the Page_Load event of the page: Application.Lock(); if(Application["PageCounter"]==null) Application[ "PageCounter" ]=0; Application[ "PageCounter" ]=(int)Application[ "PageCounter" ]+1; Response.Write (Application[ "PageCounter" ]); Application.UnLock (); In the preceding example, the Lock() method is first called to ensure that the variable PageCounter cannot be simultaneously modified by another thread. Next, the counter is increased by 1 and then the value is displayed in the browser window. At last, the UnLock() methods is called to release the imposed lock on the application state variable PageCounter. You can notice the effect of Lock() and UnLock() methods by clicking the Refresh button on the browser window. Each time you click the Refresh button, the value of the PageCounter is incremented. You can open the same form in another window by copying and pasting the URL, you will note that the value displayed will be not 1 but 1 incremented to the value you left on the previous window. Note: If you do not explicitly call the Unlock( ) method, the .NET Framework automatically removes the lock when the request completes or times out, or when an unhandled error occurs during request execution and causes the request to fail. This automatic unlocking prevents the application from deadlocking. You cannot selectively lock items in an application state, the application state object as a whole is blocked. Advantages of using Application state: Application state is easy to use and is consistent with other .NET Framework classes. Storing information in application state involves maintaining only a single copy of information. Limitations in using Application State:
The data stored in an application state is lost when the Web server containing the application state fails due to a server crash, upgrade, or shutdown. Application state requires server memory and can affect the performance of the server and the scalability of the Web application.
Session State
In ASP.NET, session state is used to store session-specific information for a Website. Unlike application state, the scope of session state is limited to the current browser session. If different users are accessing a Web application, each will have a different session state. In addition, if a user exits and returns later, the user will have a different session state. The session state has a built in support in ASP.NET. The built-in session state feature automatically performs the following actions: Identify and classify requests coming from a browser into a logical application session on the server. Store session-specific data on the server for use across multiple browser requests. Raise session lifetime-related events, such as Session_OnStart and Session_OnEnd, which can be handled using application code. A unique 120-bit SessionID string containing ASCII characters identifies and tracks each active ASP.NET session. The following code can be used to add the variable name myvar in the session state: Session["myvar"] = "HELLO"; To display the value of my var, you can use the following statement: Response.Write(Session["myvar"]); There are three issues that you need to consider while adding variables and objects to a session state: Any variable or object that you add to a session state is available only until the user closes the browser window. The variables and the objects are explicitly removed form session state if the user does not requests a page for more than 20 minutes. Any variable or object added to a session state is related to a particular user. For example, you can store different values for myvar for two different users accessing the Web page and each user can access only the value that is assigned to him. Any object that supports serialization can be added to a session state. Since objects stored in a session state are stored on the server, session objects are not subject to the same size limitations as cookies. Similar to he application state you can remove a variable added to the session state by using the Remove ( ) or RemoveAll ( ) method. Starting and Ending a User Session
A user session starts when a user requests the first page from a Web Site. when the first page is requested, the Web server adds the ASP.NET SessionID cookie to the client computer. The following line enables you to view the value of session ID: Response.Write(Session.SessionID); You can use the Abandon() method of the Session object to explicitly stop a user session, by using the following line of code: Session.Abandon(); You can also modify the Timeout property of the Session object to change the default value of timeout i.e. 20 minutes. To do this open the Web.Config file and scroll to the <sessionState> section and modify the value of the Timeout property as shown in the shaded portion of the figure below:
Handling Session events Session states has the following events that you can capture to manipulate an ASP.NET web application.: Session_Start Session_End You can capture both these events by adding subroutines in the Global.asax file. A practical demonstration in the end of this article would make the usage of these events more clear. You can also use the Session_Start subroutine to automatically redirect users to a particular page when a user session begins.
Since the session data is stored on the Web server, and if the server crashes the session data would be lost. To avoid this, you can store session data in a separate Windows service. For this perform the following steps: Create a new ASP.NET web application. Search the ASP.NET State service and click the Start Service button. Modify the web.Config file to set the sessionstate mode to the value StateServer and specify the location of the State Server in the stateConnectionString. When the State Server is used to store the session state data, the process mode is InProc ( in-process), by default. You can also manage session state out of process by storing data in a Microsoft SQL Server database. The advantage is that you can cluster multiple database servers so that if one fails, another can take over the state management. For this change the mode property of sessionState in Web.config to SQLServer. By default session state is enabled for all pages of your application. Although you can disable it by modifying the page directive as follows: <%@ Page EnableSessionState = "False" %> Advantages of using Session state: It is event driven, therefore you can use session events to perform conditional execution of user-defined tasks. data in session state variables and objects can survive Web server restarts without losing data. Session states can be used in both multi-computer and multi-process configurations. A session state facility can work with browsers that do not support HTTP cookies. Limitations in using Session State: Session state variables are stored in memory until they are either removed or replaced. This can degrade Web server performance.
Practical Demonstration
Here we'll build a simple page that resembles some of the characteristics of a shopping cart. It will show number of items in the cart which may vary according as per every user who's logged on to this page. We will also have a button to remove items from the cart. I have kept it simple, and I am just counting the number of items in the cart, you can do this by assigning the value to the session variable as a DataSet or an Arraylist. For the sake of your learning I will also show you the usage of application state and the session events. You can download example project from this link . This application keeps a track of the number of users who have visited the site. Had you wanted to keep a track of just the active users, you could have added the following line of code to the Session_End event. Application["Counter"] = (int)Application["Counter"]-1;
Implementation
To implement page output caching, simply add an OutputCache directive to the page.
Duration
Required. Time, in seconds, the page should be cached. Must be a positive integer. Specifies where the output should be cached. If specified, must be one of: Any, Client, Downstream, None, Server or ServerAndClient. Required. The names of the variables in the Request, which should result in, separate cache entries. "none" can be used to specify no variation. "*" can be used to create new cache entries for every different set of variables. Separate variables with ";". Varies cache entries based on variations in a specified header. Allows custom variations to be specified in the global.asax (for example, "Browser").
Location
VaryByParam
VaryByHeader VaryByCustom
Most situations can be handled with a combination of the required Duration and VaryByParam options. For instance, if you have a product catalog that allows the user to view pages of the catalog based on a categoryID and a page variable, you could cache it for some period of time (an hour would probably be acceptable unless the products change all the time, so a duration of 3600 seconds) with a VaryByParam of "categoryID;page". This would create separate cache entries for every page of the catalog for each category. Each entry would persist for one hour from its first request. VaryByHeader and VaryByCustom are primarily used to allow customization of the page's look or content based on the client that is accessing them. Perhaps the same URL renders output for both browsers and mobile phone clients, and needs to cache separate versions based on this. Or perhaps the page is optimized for IE but needs to be able to degrade gracefully for Netscape or Opera (instead of just breaking). This last example is common enough that we'll show an example of how to do it: Example: VaryByCustom to support browser customization In order to enable separate cache entries for each browser, VaryByCustom can be set to a value of "browser". This functionality is built into the caching module, and will insert separate cached versions of the page for each browser name and major version.
%>
However, there may be other parts of the page that are common to the entire application. These are perfect candidates for caching, using fragment caching and user controls. Menus and other layout elements, especially ones that are dynamically generated from a data source, should be cached with this technique. If need be, the cached controls can be configured to vary based on the changes to its controls (or other properties) or any of the other variations supported by page level output caching. Hundreds of pages using the same controls can also share the cached entries for those controls, rather than keeping separate cached versions for each page.
Implementation
Fragment caching uses the same syntax as page level output caching, but applied to a user control (.ascx file) instead of to a web form (.aspx file). All of the attributes supported by the OutputCache directive on a web form are also supported for user controls except for the Location attribute. User controls also support an OutputCache attribute called VaryByControl, which will vary the caching of the user control depending on the value of a member of that control (typically a control on the page, such as a DropDownList). If VaryByControl is specified, VaryByParam may be omitted. Finally, by default each user control on each page is cached separately. However, if a user control does not vary between pages in an application and is named the same across all such pages, the Shared="true" parameter can be applied to it, which will cause the cached version(s) of the user control to be used by all pages referencing that control. Examples
Insert() method is used. Each of these has several overloads. The only difference between Add() and Insert() is that Add() returns a reference to the cached object, while has no return value (void in C#, Sub in VB). Examples
More Options
In addition to the dependencies described above, we can also specify the priority of the item (from low to high to NotRemovable, defined in the System.Web.Caching.CacheItemPriority enumeration) and a CacheItemRemovedCallback function to call when the item expires from the cache. Most of the time, the default priority will sufficelet the caching engine do what it's good at and handle the cache's memory management. The CacheItemRemovedCallback option allows for some interesting possibilities, but in practice it too is rarely used. However, to demonstrate the technique, I'll provide an example of its usage: CacheItemRemovedCallback example
TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Default, callback); . . . public static void OnRemove(string key, object cacheItem, System.Web.Caching.CacheItemRemovedReason reason) { AppendLog("The cached value with key '" + key + "' was removed from the cache. reason.ToString()); }
This will log the reason for the expiration of data from the cache using whatever logic is defined in the
Reason: " +
AppendLog() method (not included heresee Writing Entries to Event Logs. Logging items as they are
removed from the cache and noting the reason for removal might allow you to determine if you are using the cache effectively, or if you might need to increase the memory on your server. Note that the callback is a static (Shared in VB) method, which is recommended since otherwise an instance of the class holding the callback function will be kept in memory to support the callback (which isn't necessary for a static/Shared method). One potential use for this feature would be to refresh cached data in the background so that users never need to wait for the data to be populated, but the data is kept relatively fresh. Unfortunately in practice, this doesn't work very well with the current version of the caching API, because the callback doesn't fire or complete execution prior to the cached item being removed from the cache. Thus, a user will frequently make a request that will try to access the cached value, find that it is null, and be forced to wait for it to repopulate. In a future version of ASP.NET, I would like to see an additional callback, which might be called CachedItemExpiredButNotRemovedCallback, which if defined must complete execution before the cached item is removed.
public DataTable GetCustomers(bool BypassCache) { string cacheKey = "CustomersDataTable"; object cacheItem = Cache[cacheKey] as DataTable; if((BypassCache) || (cacheItem == null)) { cacheItem = GetCustomersFromDataSource(); Cache.Insert(cacheKey, cacheItem, null, DateTime.Now.AddSeconds(GetCacheSecondsFromConfig(cacheKey), TimeSpan.Zero); }
return (DataTable)cacheItem; }
There are several points I'd like to make about this pattern: Values, like cacheKey, cacheItem and the cache duration, are defined once and only once. The cache can be bypassed as neededfor example, after registering a new customer and redirecting to a list of customers, it would probably be best to bypass the cache and repopulate it with the latest data, which would include the newly inserted customer.
Cache is only accessed once. This has performance benefits and ensures that NullReferenceExceptions don't occur because the item was present the first time it was checked but had expired before the second check.
The pattern uses strong type checking. The "as" operator in C# will attempt to cast an object to a type and will simply return null if it fails or if the object is null. The duration is stored in a configuration file. All cache dependencies, whether file-based, time-based, or otherwise, should ideally be stored in a configuration file so that changes can be made and performance measured easily. I also recommend that a default cache duration be specified, and that the GetCacheSecondsFromConfig() method uses the default if no duration is specified for the cacheKey being used.
The associated code sample is a helper class that will handle all of the above but allow cached data to be accessed with one or two lines of code. Download CacheDemos.msi.
Summary
Caching can provide huge performance benefits to applications, and should therefore be considered when an application is being designed as well as when it is being performance tested. I have yet to encounter an application that could not benefit from caching in some capacity, though certainly some applications are better suited than others. A solid understanding of the caching options available in ASP.NET is an important skill for any ASP.NET developer to master.
Adding an item to the cache by directly setting the item via key and value. Adding items to the cache using the Insert method.
Adding an item to the cache and adding a dependency so that the item is removed from the cache when the dependency changes. You can set dependencies based on other cache items, on files, and on multiple objects.
Adding an item to the cache with expiration policies. In addition to being able to set an item's dependency, you can set the item to expire after a period of time (a sliding expiration) or at a specific time (an absolute expiration). You can define either an absolute expiration or a sliding expiration, but not both.
Adding an item to the cache and defining the relative priority of the cached item. Relative priorities help the .NET Framework determine what cache items to remove; lower priority items are removed from the cache before higher priority items.
In addition to the dependencies shown here, you can create a dependency on a SQL Server table or based on a custom dependency. For more information, see ASP.NET Caching Overview and Caching in ASP.NET with the SqlCacheDependency Class. You can also have the application cache notify your application when the item is removed from the cache, using the CacheItemRemovedCallback delegate. For a full example, see How to: Notify an Application When an Item Is Removed from the Cache.
To add an item to the cache by directly setting the item via key and value
Add items to the cache as you would add items to a dictionary by specifying the item's key and value. The following code example adds an item named C#
CacheItem2:
The following code example adds an item named the cache named C#
CacheItem2:
string[] dependencies = { "CacheItem2" }; Cache.Insert("CacheItem3", "Cached Item 3", new System.Web.Caching.CacheDependency(null, dependencies));
Visual Basic
Dim dependencies As String() = {"CacheItem2"} Cache.Insert("CacheItem3", "Cached Item 3", _ New System.Web.Caching.CacheDependency( _ Nothing, dependencies)) The following code example shows an item named CacheItem4 added to the cache and having a file
dependency set on the file named XMLFile.xml: C#
System.Web.Caching.CacheDependency dep1 = new System.Web.Caching.CacheDependency(Server.MapPath("XMLFile.xml")); string[] keyDependencies2 = { "CacheItem1" }; System.Web.Caching.CacheDependency dep2 = new System.Web.Caching.CacheDependency(null, keyDependencies2); System.Web.Caching.AggregateCacheDependency aggDep = new System.Web.Caching.AggregateCacheDependency(); aggDep.Add(dep1); aggDep.Add(dep2); Cache.Insert("CacheItem5", "Cached Item 5", aggDep);
Visual Basic
Dim dep1 As CacheDependency = _ New CacheDependency(Server.MapPath("XMLFile.xml")) Dim keyDependencies2 As String() = {"CacheItem1"} Dim dep2 As CacheDependency = _ New System.Web.Caching.CacheDependency(Nothing, _ keyDependencies2) Dim aggDep As AggregateCacheDependency = _ New System.Web.Caching.AggregateCacheDependency() aggDep.Add(dep1) aggDep.Add(dep2) Cache.Insert("CacheItem5", "Cached Item 5", aggDep)
Cache.Insert("CacheItem7", "Cached Item 7", null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 10, 0));
Visual Basic
Cache.Insert("CacheItem7", "Cached Item 7", _ Nothing, System.Web.Caching.Cache.NoAbsoluteExpiration, _ New TimeSpan(0, 10, 0))
To add an item to the Cache with priority settings
Call the Insert method, specifying a value from the CacheItemPriority enumeration. The following code example adds an item to the cache with a priority value of High: C#
C#
string CachedItem9 = (string)Cache.Add("CacheItem9", "Cached Item 9", null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null);
Visual Basic
Dim CachedItem9 As String = CStr(Cache.Add("CacheItem9", _ "Cached Item 9", Nothing, _ System.Web.Caching.Cache.NoAbsoluteExpiration, _ System.Web.Caching.Cache.NoSlidingExpiration, _ System.Web.Caching.CacheItemPriority.Default, _ Nothing))
Overview
In this month's column, I'll discuss the following topics:
What are user controls? What are custom controls? What are the basic differences between user controls and custom controls?
I'll also introduce a few of the advanced topics that concern custom controls, such as state management and the rendering of custom controls.
<script language="C#" runat="server"> public void button1_Click(object sender, EventArgs e) { label1.Text = "Hello World!!!"; } </script>
2. Create a user interface for the user control.
<asp:Label id="label1" runat="server"/> <br><br> <asp:button id="button1" text="Hit" OnClick="button1_Click" runat="server" />
How to use a user control in a Web Forms page
1. Create a new Web Forms page (.aspx) in Microsoft Visual Studio .NET 2002, Microsoft Visual Studio .NET 2003, Microsoft Visual Studio 2005, or any text editor. 2. Declare the @ Register directive. For example, use the following code.
<html> <body> <form runat="server"> <UC:TestControl id="Test1" runat="server"/> </form> </body> </html>
How to create an instance of a user control programmatically in the code behind file of a Web Forms page
The previous example instantiated a user control declaratively in a Web Forms page using the @ Register directive. However, you can instantiate a user control dynamically and add it to the page. Here are the steps for doing that: 1. Create a new Web Forms page in Visual Studio. 2. Navigate to the code behind file generated for this Web Forms page. 3. In the Page_Load event of the Page class, write the following code.
// Load the control by calling LoadControl on the page class. Control c1 = LoadControl("test.ascx"); // Add the loaded control in the page controls collection. Page.Controls.Add(c1);
Note You can add a user control dynamically at certain events of the page life cycle. For more information, visit the following Web sites: Adding controls to a Web Forms page programmatically http://msdn2.microsoft.com/en-us/library/kyt0fzt1(vs.71).aspx Control execution lifecycle http://msdn2.microsoft.com/en-us/library/aa719775(vs.71).aspx Dynamic Web controls, postbacks, and view state, by Scott Mitchell http://aspnet.4guysfromrolla.com/articles/092904-1.aspx
The page parser parses the .ascx file specified in the Src attribute in the @ Register directive and
generates a class that derives from the System.Web.UI.UserControl class.
The parser then dynamically compiles the class into an assembly. If you are using Visual Studio, then at design time only, Visual Studio creates a code behind file for the
user control, and the file is precompiled by the designer itself.
Finally, the class for the user control, which is generated through the process of dynamic code
generation and compilation, includes the code for the code behind file (.ascx.cs) as well as the code written inside the .ascx file.
You should derive from System.Web.UI.Control if you want the control to render nonvisual
elements. For example, <meta> and <head> are examples of nonvisual rendering.
You should derive from System.Web.UI.WebControls.WebControl if you want the control to render
HTML that generates a visual interface on the client computer. If you want to change the functionality of existing controls, such as a button or label, you can directly derive the new class with these existing classes and can change their default behavior. In brief, the Control class provides the basic functionality by which you can place it in the control tree for a Page class. The WebControl class adds the functionality to the base Control class for displaying visual content on the client computer. For example, you can use the WebControl class to control the look and styles through properties like font, color, and height.
How to create and use a simple custom control that extends from System.Web.UI.Control using Visual Studio
1. 2. 3. 4. 5. Start Visual Studio. Create a class library project, and give it a name, for example, CustomServerControlsLib. Add a source file to the project, for example, SimpleServerControl.cs. Include the reference of the System.Web namespace in the references section. Check whether the following namespaces are included in the SimpleServerControl.cs file.
protected override void Render(HtmlTextWriter writer) { writer.Write("Hello World from custom control"); }
Note The HtmlTextWriter class has the functionality of writing HTML to a text stream. The Write method of the HtmlTextWriter class outputs the specified text to the HTTP response stream and is the same as the Response.Write method. 8. Compile the class library project. It will generate the DLL output. 9. Open an existing or create a new ASP.NET Web application project. 10. Add a Web Forms page where the custom control can be used. 11. Add a reference to the class library in the references section of the ASP.NET project. 12. Register the custom control on the Web Forms page.
<%@ Register TagPrefix="CC " Namespace=" CustomServerControlsLib " Assembly="CustomServerControlsLib " %>
13. To instantiate or use the custom control on the Web Forms page, add the following line of code in the <form> tags.
Now that you have built a simple custom control, let's look at how to expose properties and apply designtime attributes on that custom control.
{ private int noOfTimes; public int NoOfTimes { get { return this.noOfTimes;} set { this.noOfTimes = value; } } protected override void Render (HtmlTextWriter writer) { for (int i=0; i< NoOfTimes; i++) { write.Write("Hello World.."+"<BR>"); } } }
3. Compile the custom control. 4. To use the custom control on the Web Forms page, add the new property to the control declaration.
The custom control that you built in the previous example works as expected. However, if you want to use that control in Visual Studio, you may want the NoOfTimes property to be automatically highlighted in the Properties window whenever the custom control is selected at design time. To make this happen, you need to provide the metadata information to Visual Studio, which you can do by using a feature in Visual Studio called attributes. Attributes can define a class, a method, a property, or a field. When Visual Studio loads the custom control's class, it checks for any attributes defined at the class, method, property, or field level and changes the behavior of the custom control at design time accordingly. To find more information about attributes, visit the following MSDN Web site: http://msdn2.microsoft.com/en-us/library/Aa288059(VS.71).aspx Let's build a sample that uses commonly used attributes: 1. Open SimpleServerControl.cs in a text editor. 2. Introduce some basic attributes at the class level, for example, DefaultProperty, ToolboxData, and TagPrefixAttrbute. We'll build our sample on these three attributes.
[ // Specify the default property for the control. DefaultProperty("DefaultProperty"), // Specify the tag that is written to the aspx page when the // control is dragged from the Toolbox to the Design view. // However this tag is optional since the designer automatically // generates the default tag if it is not specified. ToolboxData("<{0}:ControlWithAttributes runat=\"server\">" + "</{0}:ControlWithAttributes>") ] public class ControlWithAttributes : Control { private string _defaultProperty; public string DefaultProperty { get { return "This is a default property value"; } set { this._defaultProperty = value; } } protected override void
What are the basic differences between user controls and custom controls?
Now that you have a basic idea of what user controls and custom controls are and how to create them, let's take a quick look at the differences between the two.
Factors
User control
Custom control Designed so that it can be used by more than one application Deployed either in the application's Bin directory or in the global assembly cache Distributed easily and without problems associated with redundancy and maintenance
Deployment Designed for single-application scenarios Deployed in the source form (.ascx) along with the source code of the application If the same control needs to be used in more than one application, it introduces redundancy and maintenance problems Creation Creation is similar to the way Web Forms pages are created; well-suited for rapid application development (RAD) A much better choice when you need static content within a fixed layout, for example, when you make headers and footers Writing doesn't require much application designing because they are authored at design time and mostly contain static data
Content
More suited for when an application requires dynamic content to be displayed; can be reused across an application, for example, for a data bound table control with dynamic rows Writing from scratch requires a good understanding of the control's life cycle and the order in which events execute, which is normally taken care of in user controls
Design
Advanced topics
Next, let's take a look at a few of the advanced features that you may use while developing custom controls.
State management
Web applications are built on HTTP, which is stateless. A page and its child controls are created on every request and are disposed of after the request is over. To maintain state in classic ASP programming, you use session and application objects. But for that, you need to do lots of coding. To avoid this, ASP.NET provides a mechanism known as view state for maintaining state across several requests. To learn more about state management and view state, visit the following MSDN Web sites:
Introduction to Web Forms state management http://msdn2.microsoft.com/en-us/library/75x4ha6s(vs.71).aspx The ASP.NET view state http://msdn.microsoft.com/msdnmag/issues/03/02/cuttingedge/default.aspx Saving Web Forms page values using view state http://msdn2.microsoft.com/en-us/library/4yfdwycw(vs.71).aspx
Example using view state in a custom control
ViewStateExample.cs
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Text; namespace ServerControlLib { /// <summary> /// When a page framework reloads this control after postback, it /// will restore the values which are in view state. /// This control can easily perform state management without /// implementing our own logic for maintaining the state. /// </summary> public class ViewStateExample : WebControl { // Text to be displayed in Text box control. private string _text; /* * This property needs to be populated before every * postback in order to * retain its value. */ public string Text { get { return (_text == null) ? "_text property is empty" : _text; } set { _text = value; } } /* * This property needs to be filled once and should be * available on the successive postbacks. */ public string TextInViewState { get { object o = ViewState["TextInViewState"]; return (o == null) ? "View state is empty" : (string)o; } set { ViewState["TextInViewState"] = value; } } /* * Over-ridden method on WebControl base class which * displays both of the property values * i.e. one stored in view state and other which is not * saved in view state. */ protected override void RenderContents(HtmlTextWriter writer) { writer.Write("Text Without View State = "); writer.Write(Text); writer.Write("<hr><br>"); writer.Write("Text In View State = "); writer.Write(TextInViewState); } } }
Example using the previous control on a Web Forms page
ViewStateExampleDemo.aspx
<%@ Page Language="C#" %> <%@ Register TagPrefix="CC" Namespace="ServerControlLib" Assembly = "ServerControlLib" %> <html> <head> <script runat="server"> void button1_Click(object sender, EventArgs e) { Control1.Text = textbox1.Text; Control1.TextInViewState = textbox2.Text; } </script> </head> <body> <form runat="server" ID="Form1"> <br> Property Value Without View State: <asp:TextBox id="textbox1" runat="server" /> <br> Property Value with View State: <asp:TextBox id="textbox2" runat="server" /> <asp:Button text="Cause Postback" onClick="button1_Click" id="button1" Runat="server" /> Output from the ViewStateExample Control : <CC:ViewStateExample id="Control1" runat="server"/> </form> </body> </html>
Rendering
In this section, I'll briefly describe what methods you should override when you derive a custom control from either the Control class or the WebControl class.
Rendering methods of the System.Web.UI.Control class
For information about the rendering methods of the System.Web.UI.Control class, visit the following MSDN Web sites:
Every page has a control tree that represents a collection of all the child controls for that page. To render the control tree, an object of the HtmlTextWriter class is created that contains the HTML to be rendered on the client computer. That object is passed to the RenderControl method. In turn, the RenderControl method invokes the Render method. Then, the Render method calls the RenderChildren method on each child control, making a recursive loop until the end of the collection is reached. This process is best explained by the following example code.
public void RenderControl(HtmlTextWriter writer) { // Render method on that control will only be called if its visible property is true. if (Visible) { Render(writer); } } protected virtual void Render(HtmlTextWriter writer) { RenderChildren(writer); } protected virtual void RenderChildren(HtmlTextWriter writer) { foreach (Control c in Controls) { c.RenderControl(writer); } }
Rendering methods of the System.Web.UI.WebControl class
For information about the rendering methods of the System.Web.UI.WebControl class, visit the following MSDN Web sites: WebControl.RenderBeginTag method http://msdn2.microsoft.com/enus/library/system.web.ui.webcontrols.webcontrol.renderbegintag(vs.71).aspx WebControl.RenderContents method http://msdn2.microsoft.com/enus/library/system.web.ui.webcontrols.webcontrol.rendercontents(vs.71).aspx WebControl.RenderEndTag method http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.webcontrol.renderendtag(vs.71).aspx
How the rendering of the WebControl class takes place
The following code example shows the Render method for the custom control.
Conclusion
That's all for now on user controls and custom controls in ASP.NET 1.0 and ASP.NET 1.1. I hope that this column helps you understand the basic differences between them and the various approaches you can take to develop them. Thank you for your time. We expect to write more about the advanced topics for custom controls, such as state management, control styles, composite controls, and design-time support for custom controls, in the near future. For more information about controls, visit the following MSDN Web sites: ASP.NET server control development basics http://msdn2.microsoft.com/en-us/library/aa310918(vs.71).aspx An extensive examination of user controls
http://msdn2.microsoft.com/en-us/library/ms972975.aspx Building templated custom ASP.NET server controls http://msdn2.microsoft.com/en-us/library/Aa478964.aspx Events in ASP.NET server controls http://msdn2.microsoft.com/en-us/library/aa720049(vs.71).aspx Composite control vs. user control http://msdn2.microsoft.com/en-us/library/aa719735(vs.71).aspx Developing ASP.NET server controls http://msdn2.microsoft.com/en-us/library/aa719973(vs.71).aspx Developing custom controls: Key concepts http://msdn2.microsoft.com/en-us/library/aa720226(vs.71).aspx Adding design-time support to ASP.NET controls http://msdn2.microsoft.com/en-us/library/Aa478960.aspx