Building ASP.NET Web Applications

I l @ ve RuBoard

Like ASP, ASP.NET automatically associates all executable pages and components under a particular virtual directory into a single Web application. In a Web application, certain state and functionality is often shared between all of those components . We'll look at the handling of application-wide state in the next section; in this section, we'll concentrate on application-wide functionality and configuration.

When people talk about a "Web application," they generally mean an instance of a particular application executing in the context of a Web server ”analogous to an instance of a desktop application. You can have multiple instances of the same Web application running on different Web servers or even, given the appropriate configuration, running on the same Web server at the same time. The application-wide settings discussed here relate to a single instance of the Web application and can be changed between instances.

Two files are used to configure a Web application: Web.config and Global.asax. We'll look at these in turn .

Web.config

The Web.config file contains information such as authorization settings, the default language, and how errors are handled. We'll look at these settings in detail shortly, but in general terms the Web.config file is a standard XML-based .NET application configuration file with some Web-specific elements. The overall structure of the file is contained in an XML configuration element, which in turn houses a system.web element:

 <?xmlversion="1.0" encoding="utf-8" ?> <configuration> <system.web> <!--Webapplicationconfigurationinhere--> </system.web> <configuration> 

To change the configuration for an application, you simply edit the file using a suitable editor. The ASP.NET runtime will build and cache the configuration for an application on first invocation. The configuration will be automatically reloaded when the runtime detects that the file has changed.

If necessary, you can have different Web.config files located in different directories in your application. This can be useful when different Web pages require different handling and configuration. By default, each child directory inherits the settings of the application's top-level virtual directory. You can use a directory-specific Web.config file to override the default settings if needed. You can also use the location element to set different settings for different parts of your application, as you'll see later when we discuss security.

Global.asax

The Global.asax file is the place to initialize application-wide objects and event handlers. This file is a special instance of an ASP.NET page, and as such it has an HTML part and an associated code-behind file. (In the case of J#, this is Global.asax.jsl.) The Global class is a subclass of System.Web.HttpApplication , whose primary purpose is to handle application life-cycle events. By default, the Global.asax file doesn't have a great deal in it ”just one line similar to the following:

 <%@ApplicationCodebehind="Global.asax.jsl" Inherits="FourthCoffee.Global" %> 

Note

In Visual Studio, double-clicking on the Global.asax file will display a [Design] screen. To view and edit the actual file, right-click on the file. From the shortcut menu, choose Open With and then select Source Code (Text) Editor.


Events are generated throughout the application life cycle. You can define event handlers in the Global.asax.jsl code-behind file to handle these and other events, as shown in the code listing for a default Global.asax.jsl.

Global.asax.jsl
 packageFourthCoffee; importSystem.Collections.*; importSystem.ComponentModel.*; importSystem.Web.*; importSystem.Web.SessionState.*; importSystem.IO.*; /** *SummarydescriptionforGlobal. */ publicclassGlobalextendsSystem.Web.HttpApplication { publicGlobal() { InitializeComponent(); } protectedvoidApplication_Start(System.Objectsender, System.EventArgse) { } protectedvoidSession_Start(System.Objectsender, System.EventArgse) { } protectedvoidApplication_BeginRequest(System.Objectsender, System.EventArgse) { } protectedvoidApplication_EndRequest(System.Objectsender, System.EventArgse) { } protectedvoidApplication_AuthenticateRequest(System.Objectsender, System.EventArgse) { } protectedvoidApplication_Error(System.Objectsender, System.EventArgse) { } protectedvoidSession_End(System.Objectsender,System.EventArgse) { } protectedvoidApplication_End(System.Objectsender, System.EventArgse) { } #regionWebFormDesignergeneratedcode /** *RequiredmethodforDesignersupport-donotmodify *thecontentsofthismethodwiththecodeeditor. */ privatevoidInitializeComponent() { } #endregion } 

From an application standpoint, the events of interest are the application start and end events and the request start and end events. We'll discuss the events raised for errors, security, and session handling later. Because the Global class defined in Global.asax inherits from System.Web.HttpApplication , in each event handler you have access to methods , properties, and events defined in this class. These include the intrinsic objects you'd find on a typical ASP.NET page, such as the Request , Response , Server , Application state, and Session state objects.

Deploying an ASP.NET Application

As with any .NET Framework application, you can deploy an ASP.NET application by simply using xcopy to copy the files to an appropriate directory on the target server. However, this does not configure the appropriate settings required by IIS, such as indicating that the virtual directory being created contains a Web application. Instead, you should use Visual Studio .NET to create a Web Setup project. This project will build a Windows Installer (MSI) file that contains functionality to set up the correct IIS settings as the application is deployed.

To do this, select Web Setup project from under the Setup and Deployment Projects folder in the New Project dialog box. Add this project to the solution that contains the files you want to deploy. The Web Setup project presents you with various views of the deployment information. In the File System view, you can change the properties of the Web Application Folder that represents the virtual directory into which your application will be deployed. In the Properties window, you can set the name of the virtual directory, default document, and so forth. You should add the Content Files and Primary Output from your Web application project (for example, the FourthCoffee project) to the Web Application Folder of the Web deployment project so that these are bundled into the installer file. You can then build the installer file by building the Web deployment project.

You can use the installer to deploy the Web application on your own system or on another target server. An example of a Web Setup project is provided in the CakeDeployment sample project. This sample project creates an installer file for the FourthCoffee Web application.

Managing State

In most applications, you'll want to maintain information, or state. You might need to pass this information between pages in various scenarios:

  1. When you need to pass information between two or more pages of an application as the user navigates between them. An example is a Web-based shopping cart in which order information is accumulated over many page views (and hence many client requests ).

  2. When you need to store information on the server between repeated requests to the same page, such as when you use a Web Form (as described earlier).

  3. When you need to pass information between two pages of an application that are used to handle a single client request. Your application might invoke an ASP.NET page that performs some calculation, data retrieval, or routing before forwarding the request to another ASP.NET page that generates the HTML output.

ASP.NET provides three state management mechanisms that let you store state in the scope of a request, a session, or the whole application. These mechanisms can help manage state in the scenarios just described.

Session Scope

As noted previously, the lifetime of a page instance is tied to the request that it services. This means that you cannot use instance data ”the traditional bastion of state in object-oriented applications ”to save state in the first two scenarios listed above. To overcome this, ASP.NET continues and extends the concept of a Session object that was introduced in ASP. This Session object provides a persistent collection on the server side that can be associated with a particular user. There's no magic to this ”the ASP.NET runtime simply uses cookies or URL rewriting to keep track of a particular user. When the user first accesses a page that is indicated to be part of a session, a new session is created and assigned a 128-bit number to identify it. This number is passed back and forth between the client and the server to identify multiple requests that constitute part of the same session.

Pages in a Web application are automatically provided with session state access unless they explicitly indicate that they do not want to be part of a session by setting the attribute EnableSessionState="false" in the <%@ Page %> directive. If a page is part of a session, you can store session state in the Session property of the Page . You use the Add method to add a data item and give it a name (or key):

 protectedSystem.Web.UI.WebControls.TextBoxName; get_Session().Add("customer",Name.get_Text()); 

Later, you can then retrieve this information using the key as the parameter of the get_Item property accessor method of the Session object:

 protectedSystem.Web.UI.WebControls.LabelWelcome; System.Stringname=(System.String)get_Session().get_Item("customer"); if(name!=null&&name.get_Length()!=0) { Welcome.set_Text("Welcome " +name); } 

You can see this in action in the code of the sample files FeedsHowManyWithSession.aspx and Login.aspx. In FeedsHowManyWithSession.aspx, you're presented with a hyperlink in the upper right corner that offers you the chance to re-login. This takes you to the Login.aspx page you saw earlier in Figure 16-14. The Click event handler for the Login button stores the login name supplied in the Session object before returning you to FeedsHowManyWithSession.aspx. The Page_Load method of FeedsHowManyWithSession.aspx sets a greeting using the name retrieved from the Session object, as shown in Figure 16-16.

Figure 16-16. The Session object provides a way of sharing information between pages.

Caution

Be careful when you retrieve data from a session. The type returned by the get_Item method is an Object , so you must know which type you expect and cast it appropriately.


Only objects can be stored in a session, not primitive types such as integers. If you need to store a primitive type, you should encapsulate it in an instance of its .NET Framework wrapper class. If you need to reset a value stored in the session, you simply use the Add method again with a different value. To remove a value, you use the Remove method:

 get_Session().Remove("customer"); 

Caution

The keys used by the Session are not case sensitive, so do not try to store multiple variables differentiated by case alone. If you do so, you might get an unwanted surprise when you remove one of them!


You can use the RemoveAll method or Clear method to clear out all of the values stored in a session:

 get_Session().Clear(); 

In addition to storing objects in the session dynamically, you can also define static objects in the Global.asax file. A static object is instantiated automatically by the ASP.NET runtime. You can define a static object by adding an element to the Global.asax file (not its code-behind):

 <%@ApplicationCodebehind="Global.asax.jsl" Inherits="FourthCoffee.Global" %> <objectid="catalog" runat="server" scope="session" class="System.Xml.XmlDataDocument" /> 

The line containing the <object> element defines a static object called "catalog" that is an instance of System.Xml.XmlDataDocument . Because the scope attribute is set to " session" , every session will have one of these objects created for it. The HttpSessionState object has a StaticObjects property that contains an instance of HttpStaticObjectsCollection . You can use the GetObject method of the HttpStaticObjectsCollection to retrieve a static object defined in this way:

 System.Xml.XmlDataDocumentdoc=(System.Xml.XmlDataDocument)get_Session(). get_StaticObjects().GetObject("catalog"); 

If you need to perform any once-only session initialization, you can place the code in the Session_Start method that's defined in the code section of Global.asax. There's also a matching Session_End event into which you can add code to perform tasks such as persisting some of the contents of the Session object. (Again, consider a shopping cart ”when the user quits the application, you might want to preserve the cart.)

Remote Session State

In ASP, you can store session state only in memory. This means that in ASP, session state is localized on the server to which the client connects. This is fine for a single-server solution, but as soon as you start operating in a multiserver environment, such as a Web farm, you have no guarantee that the client will connect to the same server each time. Therefore, you must store any session state in a location where it is accessible to every server in the farm.

You can define different ways of storing session state in the system.web section of the Web.config file using the sessionState element. You can use attributes of this element to define whether to use cookies to keep track of a session and to set a timeout for the session. The mode attribute defines the location in which the state is to be held. This location can be

  • InProc

    In-process storage of session state is the default, out-of-the-box mechanism used for state management. It is compatible with ASP. State is stored in memory on the server instance that's handling the call. As stated earlier, this is fine if your application runs on only one server, but it is not robust in the face of failure and is unsuitable for an application that uses multiple Web servers. A typical Web.config entry for in-process state management is

     <sessionStatemode="InProc" cookieless="false" timeout="20" /> 
  • StateServer

    Out-of-process storage uses the ASP.NET State Service. This is a process that runs as a Windows Service and can be started and stopped using the Services applet in the Administrative Tools window in Control Panel. The ASP.NET State Service ensures that state is maintained should the Web application, or even IIS itself, terminate unexpectedly. It also operates in a Web Farm environment, permitting multiple servers to share the same state information. The ASP.NET State Service is installed as part of .NET Framework. You indicate which server and port the state service will run on using the stateConnectionString attribute. By default, the ASP.NET State Service listens on port 42424:

     <sessionStatemode="StateServer"  stateConnectionString="tcpip=192.168.0.4:42424" cookieless="false" timeout="20" /> 
  • SQLServer

    This mode persists user state to a SQL Server database between page accesses. This mode is tolerant to machine failure because state information is preserved across machine shutdown and reboot. You should create a SQL Server database and populate it with the tables needed by the ASP.NET runtime using the SQL script InstallSqlState.sql located under "\Windows\Microsoft.NET\Framework\.NETVersion\". You can then set the sqlConnectionString attribute of the sessionState element to a SQL connection string that defines server and security information for the target instance of SQL Server:

     <sessionStatemode="SQLServer" sqlConnectionString="datasource=ALICE;userid=sa;password=" cookieless="false" timeout="20" /> 

Tip

If this is the first time you've accessed SQL Server from ASP.NET, you might encounter authorization issues. To overcome these, you can explicitly grant access to the database using the osql command-line tool supplied with the .NET Framework. First, grant permission to the ASPNET user so it can access the database:

 C:\>osqlESSQLServerHostName 1>execsp_grantloginN'ASPApplicationHostName\ASPNET' 2>go 

You then need to grant access to every database that ASPNET needs, in this case the ASPState database. To do this, execute the following osql commands:

 3>execsp_defaultdbN'ASPApplicationHostName\ASPNET',N'ASPState' 4>go 5>exit 

Caution

The contents of the mode attribute are case sensitive.


To try the state server or SQL Server state mechanisms, follow the appropriate instructions for installing them in the previous list and uncomment the appropriate part of the FourthCoffee sample project Web.config file. (Two prepared forms are provided ”you'll need to edit the addresses and datasource information and comment out the InProc entry.) You can use either the StateServer mode or SQLServer mode for applications that are installed across Web farms. Also note that despite its resilience, the SQLServer mode comes at a price ”each time you access cached data you're accessing a database, and this can be a slow operation, especially if the database is remote. Earlier, we noted that you can store any type of object in your session state. However, if you use either type of remote state storage, the objects must be serializable because they will be passed across a process boundary.

Application Scope

Some of the state that you want to cache will be used across multiple sessions (multiple users) running the application rather than on a per-session basis. To avoid having to load the same information into each session, you can use an application-scope state storage mechanism to store such state so that it is accessible from all sessions within the application.

Application-scope state is stored for the duration of the application, so it is removed only when the application shuts down. An example of state that you might want to cache across the application is a configurable set of values that are displayed in a drop-down list box. In this case, you can retrieve these values from a data source (perhaps an XML file or a database table) and store them in the Application object so that they can be quickly and easily accessed from every session.

Each Page has an Application property, which is an instance of System.Web.HttpApplicationState that represents application-scope state. This class provides the same set of accessor methods we discussed in the last section on session state ”namely, Add , Clear , Remove , and RemoveAll . The class also has Item and StaticObject properties through which you can access the contents of the object. Static objects are defined in the same way for application scope as they are for session scope. The only difference is that the object element in Web.config has the scope="application" attribute value. There are also Application_Start and Application_End event handler methods defined in Global.asax, as mentioned earlier, in which you can place per-application initialization and shutdown code.

The HttpApplicationState class also has Get and Set methods that provide access to its contents. The Get method simply provides a more intuitive way of accessing the contents of the application object than accessing through the Item property. You should use the Set method to change the value of an existing entry rather than just using Add multiple times, as you would when you change the value of an entry in the session object.

One major difference between session-scope and application-scope state storage is that application-scope storage can be accessed concurrently by multiple threads from different sessions. This means you must guard the contents of the application when you manipulate them. The HttpApplicationState class provides Lock and Unlock methods to assist with this:

 get_Application().Lock(); get_Application().Add("catalog",doc); get_Application().UnLock(); 

Application state is particular to the current instance of the application and is not shared across multiple instances in a Web farm.

You can use the System.Web.Caching.Cache instance associated with the page as an alternative to the Application object for storing application-wide state. The Cache class is discussed in more detail later in the chapter.

Request Scope

If you recall our original list of scenarios in which state must be stored, scenario 3 required state to be shared by two pages executing as part of the same request. This is the case if one page transfers a client request to another page using the Server.Transfer method (not the Response.Redirect method ”this would cause the client to start a whole new request). Obviously, you can use the session-scope or application-scope state storage mechanisms, but you can also pass data directly between pages in the same request. Essentially, you do this by adding a reference to the sending page to the receiving page and making the shared data available through property accessors on the sending page.

Cookies

If you want to keep track of your own user information, you can set your own cookies independently of ASP.NET. Consider a situation in which you want to cache the user's name to use it as the default value next time that person logs in. To achieve this, you can create an instance of the System.Web.HttpCookie class containing the user's name and then add it to the cookie collection stored in the Response object. The following code is from the Login.aspx.jsl sample file:

 privateSystem.StringfcCookieName= "FourthCoffeeUserID"; privatevoidLoginButton_Click(System.Objectsender,System.EventArgse) { //Dootherwork //Storeusernameinacookietousenexttime HttpCookiefcCookie=newHttpCookie(fcCookieName,Name.get_Text()); System.DateTimenow=System.DateTime.get_Now(); System.TimeSpanduration=newSystem.TimeSpan(0,0,20,0); fcCookie.set_Expires(now.Add(duration)); get_Response().get_Cookies().Add(colCookie); } 

The Response has a Cookies property that is a collection of HttpCookies to which you can add your new HttpCookie instance. The HttpCookie constructor has two forms ”one just takes a cookie name and the other takes a name and a string value. (Cookies can store only strings, not objects.) Note that if you do not set an expiration time, the cookie will be held in memory in the Web browser and will disappear when the browser instance is closed. The code shown above persists the cookie for 20 minutes.

You can retrieve a cookie that you previously set by accessing the cookie collection of the Request object. The Request object has a Cookies property that contains a collection representing the cookies received from the client. You can retrieve an HttpCookie object containing your required cookie by passing its name to the Get method of the collection. This code, again from the Login.aspx.jsl sample file, shows how the cookie stored earlier can be retrieved and used. The overall effect is to provide any previously supplied user name as the default value in the User Name text box on the login page:

 privatevoidPage_Load(System.Objectsender,System.EventArgse) { //Dootherwork //BasethesuggestednameintheNamefieldonthecookie //ifthereisoneset HttpCookiefcCookie=get_Request().get_Cookies().Get(fcCookieName); if(fcCookie!=null) { System.StringcookieName=fcCookie.get_Value(); if(cookieName!=null&&cookieName.get_Length()!=0) { Name.set_Text(cookieName); } } } 

Note

The use of cookies here is fairly simplistic. If you were to do this for real, you would generate a GUID to represent the user and store this as the cookie. You could then use this GUID as the primary key in a database table in which you store the user's information.


Error Handling

The standard .NET error handling mechanism is based on exceptions. When your ASP.NET code uses other classes and components, exceptions might be generated if something goes wrong. As an application developer, you must detect and handle these exceptions appropriately.

An exception usually contains a message and other information to help you figure out the cause of the exception. Generally, this information is useful to the application developer and administrator but not the end user, so you generally won't want to display the exception to the user. Instead, you'll want to log the exception type and message in some persistent way, such as writing to the application error facility on your server or logging the message through Windows Management Instrumentation (WMI). You can then handle the exception in an elegant manner using a combination of the following mechanisms:

  • Trying to recover from the error ”for example, by retrying a failed database access and then proceeding normally.

  • Performing graceful degradation, such as providing only part of the functionality for the user. This process is useful if the error is not critical and useful work can still be done.

  • Displaying a user-friendly message to the user so the user understands what is going on and has the option to rectify the problem or return to a previous screen in the application.

The next question is where you handle your errors. You can handle them in three possible ways:

  • On an application-wide basis

  • On a per-page basis

  • Locally, within the method executing at that time

We'll consider each of these in turn.

Application-Wide Error Handling

You can set an overall error handler for your application in the Global.asax file. To do this, you define a method body for the Application_Error method in the associated Global.asax.jsl file:

 protectedvoidApplication_Error(System.Objectsender,System.EventArgse) { //Logtheerror,andthendosomethingaboutit... this.get_Server().Transfer("HandledError.aspx"); } 

The method shown is from the Global.asax.jsl file in the ErrorHandling sample project. In this case, the error is passed to a specific custom error-handling page, HandledError.aspx, which is also in the ErrorHandling sample project. When the custom error page is loaded, it can retrieve the unhandled error that caused the application-level error handler to be invoked:

 privatevoidPage_Load(System.Objectsender,System.EventArgse) { //Writeauser-friendlymessagetotheuserLabel UserMessage.set_Text("Pleasetrylater"); System.Exceptionex=this.get_Server().GetLastError(); //WriteadetailedmessagetothedeveloperTextBoxes if(ex!=null) { DevMessage.set_Text(ex.get_Message()+ "\n\n" + ex.get_StackTrace()); System.Exceptionnested=ex.get_InnerException(); if(nested!=null) { NestedMessage.set_Text(nested.get_Message()+ "\n\n" + nested.get_StackTrace()); } } else { DevMessage.set_Text("Noexceptioninformationavailable"); } } 

This error page displays a user-friendly message in the UserMessage Label control. Typically, this message is retrieved from a resource file based on the type of error and the circumstances under which it occurred. The two TextBox controls, DevMessage and NestedMessage , display the information that you would typically want to record in an application log file. You can invoke this functionality from the default.htm page in the ErrorHandling sample project. Select the hyperlink for application-wide error handling, and you'll be directed to the ApplicationErrorHandling.aspx page that contains a single button. Clicking this button will generate an exception. Because the exception is not handled in the page, the application-level error handler will catch it and display the HandledError.aspx page.

As you can see from the previous code, the exception that caused this page to be displayed is available from the GetLastError method on the Server property of the page. Because no specific error handling has been set on the page, the exception reported is an HttpUnhandledException that has been used to wrap the real exception. The underlying cause, an ApplicationException , is retrieved from the outer exception's InnerException property.

The message passed to the exception when it was created, and the stack trace indicating where the exception was generated, are both available for error recording. An example of this information is shown in Figure 16-17.

Figure 16-17. Using an error page to display a user-friendly message and log exception information

How you handle the exception will depend on your application, but for an ASP.NET Web application, you might want to redirect the user to an error page that displays an appropriate message. You can do this using the Transfer method of the HttpServerUtility for the current page:

 this.get_Server().Transfer("HandledError.aspx"); 

Note

There is no simple equivalent of the Windows Forms MessageBox class. This form of modal dialog box is available only when you use client-side script to display a JavaScript Alert .


You can also specify an error page for HTTP-level errors in the Web.config file. You can set this so remote users see friendly custom error messages and local developers and administrators see the full error details:

 <customErrorsdefaultRedirect="defaulterror.aspx" mode="On"> <errorstatusCode="404" redirect="filenotfound.htm" /> </customErrors> 
Page-Specific Error Handling

Although it is useful to have error handling that spans the application, you'll frequently need more location-specific error handling. You can associate a custom error page with each ASPX page in your application by using the ErrorPage attribute of the <%@ Page %> directive:

 <%@Pagelanguage="VJ#" Codebehind="FeedsHowMany.aspx.jsl" ErrorPage="HandledErrorFromPage.aspx" AutoEventWireup="false" Inherits="FourthCoffee.FeedsHowMany" %> 

This page-specific error handling will take precedence over the application-level error handling. The sample file ErrorPageErrorHandling.aspx defines an error page; you can access it from the page ErrorHandling/default.htm by selecting the per-page error handling using an error page hyperlink. Again, this page has one large button that generates an exception, which causes the defined error page to be shown.

Although the ErrorPage attribute is convenient and easy to implement, it suffers from one major drawback ”the exception information is not propagated to the error page. To get hold of the exception information on a per-page basis, you must set an error handler when the page loads:

 privatevoidPage_Load(System.Objectsender,System.EventArgse) { System.EventHandlererrorHandler= newSystem.EventHandler(this.Page_Error); this.add_Error(errorHandler); } privatevoidPage_Error(System.Objectsender,System.EventArgse) { //Errorhandlingcodeinhere... } 

To connect the event handler, you add an EventHandler delegate to the Error property of the Page . When your target method is called, you have access to the underlying exception using the Server.GetLastError method. The exception returned from this is the exception that caused the problem ”it is not wrapped in an HttpUnhandledException . An example of per-page error handling can be seen in the sample file PageErrorHandling.aspx. You can access this page from the page ErrorHandling/default.htm by selecting the per-page error handling using the Page Error event hyperlink. Again, this page has one large button that generates an exception. This exception is caught in the page and a specific error page is shown.

Note

The interaction between page-level, application-level, and global error handling can be quite involved, especially if you do not clear the error. If you plan to use a combined approach in your application, you'd be well advised to experiment a little!


Local Error Handling

In addition to your overall application-level or page-level error handling strategy, you can also handle exceptions in-line in your ASP.NET code. To handle an exception in a method of an ASP.NET application, you can use a try/catch block as you would with any other J# application:

 privatevoidCalculate_Click(System.Objectsender,System.EventArgse) { try { //Performbusinesslogic } catch(Exceptionex) { //Handleexception } } 

As before, the action you take will be specific to your application. The sample file TryCatchErrorHandling.aspx shows this type of local exception handling; you can access it from the page ErrorHandling/default.htm by selecting the using try/catch to handle errors hyperlink. Again, this page has one large button that generates an exception, which is caught in a try/catch block.

Generating Exceptions

As an application developer, you might have defined your own set of exceptions based on System.ApplicationException that you use to signal application-specific errors:

 //Thisshouldhavebeenclearedonload,socomplainifithasn't if(get_Session().get_Item("customer")!=null) { thrownewMultipleLoginException("Multipleloginproblem"); } 

You'll want to throw these exceptions where appropriate, including from your ASP.NET pages. If you catch these exceptions using a try/catch block in the method in which they are generated, there will be no problem. But what if you want to take advantage of page-level or application-level error handling? In this case, if you do not catch the exception, it will be caught by the page-level or application-level error handling. This is fine, but there is one thing you must do to make this work in J#. The methods defined as part of the classes in the .NET Framework do not have an attached attribute indicating which exceptions they can throw. However, if you throw any exceptions in your J# code, you must add a throws clause to the signature of the method in which you throw them. If you do not do this, the compiler will complain.

The following example shows how you can throw an exception in response to a button click:

 privatevoidLoginButton_Click(System.Objectsender,System.EventArgse) throwsSystem.ApplicationException { //Thisshouldhavebeenclearedonload,socomplainifithasn't if(get_Session().get_Item("customer")!=null) { thrownewMultipleLoginException("Multipleloginproblem"); } //Wedon'thaveanyauthenticationsystemsetup, //sojustcapturetheusernameandusethistobe //friendly! get_Session().Add("customer",Name.get_Text()); get_Server().Transfer("FeedsHowMany.aspx"); } 

Security

If you're developing a professional application, security will likely be an important issue. Parts of your application might need to be restricted to authorized administrators, you might need to keep the contents of an exchange between client and server private, or you might simply need to identify your user to implement personalization features. ASP.NET has automatic security features that cater to different types of authentication and provide flexible authorization.

Authentication

ASP.NET uses the same type of declarative security configuration that you encountered with HTTP Remoting in Chapter 11. You can configure different parts of your application to require different levels of security. A user who navigates to a secure part of the application must be authenticated to determine whether he or she has the appropriate access rights.

You can indicate the type of authentication required by the application in the Web.config file, using the authentication element within the system.web element:

 <authenticationmode="Windows" /> 

The mode attribute defines the type of security to be used. In the example shown, it is set to use the underlying Windows security system, which will be based on the principals set up on your machine or domain. While this might be suitable for an intranet application, it won't be too useful if you're setting up your Web application for use by external users around the globe. In this case, you can set the authentication mode to Passport or Forms.

Passport-based authentication takes advantage of the Microsoft Passport system to authenticate users from either inside or outside the organization who have Passport accounts. Microsoft supplies the Passport SDK (available from http://www.microsoft.com/ myservices /passport/default.asp), which you can use to authenticate a user. You can create an ASP.NET page that prompts the user for the details and verifies those details using the Passport SDK. The ASP.NET configuration file should specify the URL of this page, which will be invoked automatically whenever an unauthenticated user attempts to access your application:

 <authenticationmode="Passport"> <passportredirectURL="FourthCoffeePassportAuth.aspx"> </passport> </authentication> 

Using Forms authentication, you can also set up your own custom authentication system based on credentials supplied by the user in an HTML form (a traditional Web login page). Your application can then authenticate the user in its own way using the credentials entered.

The following example shows how forms-based authentication is configured. The name attribute specifies a cookie that will be used to hold user information when the user has been authenticated. (Additional attributes are available for specifying that the cookie should be encrypted or have a limited life.) This cookie is sent back to the server on every subsequent page access, and it ensures that the user is not reprompted for credentials every time he or she accesses a restricted resource. The loginUrl attribute is the name of the ASP.NET page that will be displayed if an unauthenticated user attempts to access the system. This is the page that should perform the necessary user validation and create the authentication cookie:

 <authenticationmode="Forms"> <formsname="FourthCoffee" loginUrl="Login.aspx"> </forms> </authentication> 

In both cases, if a user is redirected to an authentication page, the location of the original page that was requested will be cached until an authentication decision is made. A user who is authenticated can be transferred to the page originally requested using the static RedirectFromLoginPage method of the System.Web.Security.FormsAuthentication class (which contains a variety of static helper methods that are useful for building pages designed to authenticate users).

If you're using forms-based authentication, it's up to you how users are verified . User details can be held in a database, held in Active Directory, or even specified using the credentials child element of the forms element in the Web.config file, as shown below. You should supply a username and password pair for each valid user. Ideally, you should also encrypt the password, specifying the algorithm used with the passwordFormat attribute. (MD5 and SHA1 are currently supported.)

 <authenticationmode="Forms"> <formsname="FourthCoffee" loginUrl="Login.aspx"> <credentialspasswordFormat="SHA1"> <username="ALongshaw" password="ADBF56F784AB63287CDF5F8092ACF5EF47139FD4"/> <username="JSharp" password="75229FDA6812B5F9A3E6FCD12950F3CA5471EE75"/> </credentials> </forms> </authentication> 

The ASP.NET Web Form that performs the authentication can then execute FormsAuthentication.Authenticate , passing in the name and password entered by the user. The Authenticate method checks the name and password against each pair of credentials stored in the Web.config file until a match is found or the list is exhausted, and it returns a Boolean value indicating whether authentication was successful, as shown in the following example. ( Name and Password are the names of Text fields on the authentication form.)

 if(FormsAuthentication.Authenticate(Name.get_Text(),Password.get_Text())) { FormsAuthentication.RedirectFromLoginPage(Name.get_Text(),false); } 
Authorization

For an authenticated user, you can use that person's principal information to assign access rights to particular resources. Again, you can use declarative security in the Web.config file with the authorization element:

 <?xmlversion="1.0" encoding="utf-8" ?> <configuration> <system.web> <authorization> <denyusers="?"/> </authorization> </system.web> </configuration> 

The example denies anonymous users (denoted by the question mark) access to all application resources. An anonymous user is any user who is not authenticated. Many Web applications, especially those used over the Internet, provide some or all of their functionality to anonymous users. The setting shown means that users who try to access the Web application must be authenticated before they can use it. You can selectively allow access to an application by combining the deny element with the allow element:

 <authorization> <denyusers="*"/> <allowusers="fred,joe,sue"/> </authorization> 

This allows only fred , joe , and sue access to the application and denies access to all other users (denoted by the asterisk). If your underlying security provider understands the concept of roles, you can also perform the authorization based on roles. If you're using Windows security, a role corresponds to a Windows group . The following example allows access to all members of the Administrators group:

 <authorization> <denyusers="*"/> <allowroles="Administrators"/> </authorization> 

While application-wide limitations are useful, most applications require more fine-grained control over access to resources so that some resources are accessible to all (such as a home page) and other resources have more stringent access restrictions. To implement this, you can specify location-specific authorization settings in the configuration element. The following example allows all users to view the Search.aspx page but limits access to any resources (such as ASPX pages) in the Admin subdirectory to members of the Administrators role:

 <configuration> <system.web> <authorization> <denyusers="?"/> </authorization> </system.web> <locationpath="Search.aspx"> <system.web> <authorization> <allowusers="*"/> </authorization> </system.web> </location> <locationpath="Admin"> <system.web> <authorization> <allowroles="Administrators"/> </authorization> </system.web> </location> </configuration> 

Note

The authorization defined in the Web.config file applies only to ASP.NET components such as ASPX files and not to plain HTML (HTM) files.


Caching

Two forms of caching are provided in ASP.NET:

  • The caching of data to be used by the application logic

  • The caching of generated page output so that it can be sent as part of subsequent responses without having to be regenerated

We'll examine both of these types of caching next.

Caching Application Data

The ASP.NET System.Web.Caching.Cache class provides an application-wide cache for data. Although it is similar to the System.Web.HttpApplicationState class in scope and intent, the Cache class provides a more sophisticated caching model. One instance of the Cache class is created automatically in each application domain, and it remains valid the entire time the application domain is active. It disappears only when the application domain is destroyed . It is accessible through the Cache property ( get_Cache ) of the current ASP.NET page. As with the HttpApplicationState class, you can store any type of object in a Cache , and the object is referenced by means of a String key. However, you can provide the Add method of the Cache with more information:

  • A timeout

    You can set a timeout in absolute or relative terms, after which the entry will expire. When an entry expires , it is removed from the cache. One parameter to Add represents an absolute time, as a DateTime instance, at which the cache entry will expire. Alternatively, or in addition, you can pass in another parameter that a represents a relative, or sliding, timeout as a TimeSpan instance. If the cache entry has not been accessed for the given relative timeout, it will expire and be discarded. The timer associated with the relative timeout will be reset every time that entry is accessed. If you do not want your data to time out, you can pass null for these parameters.

  • A dependency

    You can associate the cache entry with one or more files or directories by using the CacheDependency class. If any of the specified files or directories changes, the cache entry will expire and be discarded. You can also make one cache entry dependent on another so that it will expire when the other entry expires. Each cache entry can be dependent on multiple files, directories, and cache entries. Although the Add method takes only one CacheDependency class, you can choose from various constructors and you can construct nested CacheDependencies if required.

  • A callback

    You can pass in an instance of the CacheItemRemovedCallback that specifies a method to be called when the cache entry expires. This can potentially update the contents of the entry if appropriate, or it can persist the last value of the entry to underlying storage. The callback is provided with the entry's key and value, together with a reason for its removal.

  • A priority

    If ASP.NET starts to run low on memory, it will start removing items from the cache. You can set the priority of cache entries, which the cache manager will use when deciding which entries to evict first. The priority value can be one of the entries in the CacheItemPriority enumeration.

Here's an example of adding and retrieving a cache entry:

 DataSetcatalog=null; if(get_Cache().Get("CakeCatalog")!=null) { catalog=(DataSet)get_Cache().Get("CakeCatalog"); } else { System.StringxmlFile=get_Server().MapPath("CakeCatalog.xml"); catalog=newDataSet("catalog"); catalog.ReadXml(xmlFile); System.DateTimenow=System.DateTime.get_Now(); System.DateTimemidnight=newSystem.DateTime(now.get_Year(), now.get_Month(), now.get_Day(), 23,59,59); get_Cache().Add("CakeCatalog", catalog, newCacheDependency(xmlFile), midnight, Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null); } 

To retrieve an entry from the Cache , you call the Get method, passing in the key used to store it. As with the Session and Application objects, Cache values are stored as System.Object , so you must cast the return value appropriately.

In the example code, if no Cache entry is found for the given key, a new entry of the appropriate type (in this case, a DataSet ) will be created. When the new entry is added, no callback is provided. If the callback or dependency is not required, it can be set to null. The example sets the absolute timeout to midnight on the day that the entry is stored, but no sliding timeout is set. You can specify one timeout or the other (or no timeout), but not both. To pass in an empty timeout value, you use the fixed values NoSlidingExpiration and NoAbsoluteExpiration , which are exposed as properties of the Cache class. The example code then sets a dependency on the file that contains the XML, using the CacheDependency constructor that takes the name of a single file. This code is used by the sample file ShowXmlCatalog.aspx.jsl in the FourthCoffee project to store the XML information for the cake catalog in the Cache object.

To overwrite an existing entry, you use the Insert method. Various overloaded forms of the Insert method allow you to reset the timeouts, dependencies, and callback, as well as the value. Otherwise, you can just refresh the value while retaining the rest of the information for the entry:

 get_Cache().Insert("CakeCatalog",catalog); 

Finally, you can remove a cache entry by using the Remove method:

 get_Cache().Remove("CakeCatalog"); 

Note

The Cache operates in conjunction with the session state management features of ASP.NET, and it can be retained using the ASP.NET State Server service or even retained in a SQL Server database, depending on the setting of the <sessionState> element in the Web.config file.


Output Caching

Although it is useful to be able to dynamically generate output to send to a browser, it can also cause overhead. If the information generated does not change for every client request, you can cache the output and serve the same page multiple times without having to regenerate it. This can significantly improve performance when you generate pages based on slowly changing data. Output caching depends on both the client and the server (and any intermediate proxies) supporting HTTP 1.1.

The simplest way to enable output caching is to use the <%@ Output %> directive in your ASPX page. This tag defines a caching time as follows :

 <%@OutputCacheDuration="180" VaryByParam="None" %> 

The Duration attribute defines the amount of time, in seconds, for which the generated page will be cached. When the duration expires, the page will be regenerated and then cached again for the same amount of time.

You can use the VaryByParam attribute to cache multiple versions of a page based on the parameters passed back in a GET or POST request. To create multiple cached versions based on the value of a particular parameter, you set the value of the VaryByParam attribute to the name of the parameter. You can define multiple attributes as the basis on which a version of the page will be cached by providing a semicolon-separated list:

 <%@OutputCacheDuration="180" VaryByParam="Size;Filling" %> 

To vary the page by any parameter, you use an asterisk as the attribute value. The VaryByParam attribute must be there even if, as in the earlier example, it is set to None . You can also cache multiple versions of a page based on HTTP header values or the requesting browser.

Sometimes only parts of a page will be constant while other parts will vary for every request. You can cache the constant parts by creating user controls that represent the parts that do not change often. You can then set the output caching for that control by using the <%@ OutputCache %> directive in its ASPX file. Alternatively, you can attach the PartialCaching attribute to the class in the code-behind for the user control. Again, multiple versions of these cached user controls can be maintained based on parameters and other factors.

You can also set all of the caching properties and behavior for a page programmatically.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net