Home > Articles > Programming > ASP .NET

This chapter is from the book

This chapter is from the book

Using Application State

You can store variables and objects directly in application state. An item that has been added to application state is available within any ASP.NET page executing in the application. Items stored in application state function like global variables for an application.

NOTE

Application state is represented by the HttpApplicationState class.

The following statement, for example, creates a new item named myItem with the value Hello! in application state:

Application( "myItem" ) = "Hello!"

After this statement is executed in an ASP.NET page, the value of myItem can be retrieved within any other ASP.NET page contained in the same application. To read the value of an application variable, you can use a statement like this:

Response.Write( Application( "myItem" ) )

This statement displays the value of the application item named myItem.

You can add more complex objects, such as collections and DataSets, in an application state. (You should read the warnings in the next section before doing so, however.) For example, you can add an existing DataSet to application state like this:

Application( "DataSet" ) = dstDataSet

After you add the DataSet with this statement, you can retrieve it from application state within any ASP.NET page and display its contents.

Classic ASP

In previous versions of Active Server Pages, the Application object was frequently used to cache data. You can still use application state for this purpose. However, the ASP.NET framework includes a new object, named the Cache object, that provides you with a richer set of methods and properties for working with cached data. To learn more details about caching data, see Chapter 17, "Caching ASP.NET Applications."

You can remove an item from application state by using the Remove method like this:

Application.Remove( "myItem" )

If you want to remove all items from application state, you can use either the Clear or RemoveAll method. For example, you would use the RemoveAll method as follows:

Application.RemoveAll()

After an item is added to application state, it remains there until the application is shut down, the Global.asax file is modified, or the item is explicitly removed. So, be cautious about storing too much data in application state.

CAUTION

You can configure ASP.NET so that it automatically shuts down the currently running application and replaces it with a new one on a timed basis. (Shutting down an application on a timed basis can make your site more stable.) When the application shuts down, you lose all data stored in application state.

Understanding Application State and Synchronization

When you add items to application state, the items can be accessed within multiple pages at the same time. This fact can result in conflicts and problems. Consider the sample page in Listing 15.1.

Listing 15.1 BadPageCounter.aspx

<Script Runat="Server">

Sub Page_Load
 Application( "PageCounter" ) += 1
 lblCount.Text = Application( "pageCounter" )
End Sub

</Script>

<html>
<head><title>BadPageCounter.aspx</title></head>
<body>

This page has been requested:
<asp:Label
 ID="lblCount"
 Runat="Server" />
times!

</body>
</html>

The page in Listing 15.1 uses an item stored in application state to create a simple page counter. Every time the page is requested, the application item named PageCounter is incremented by one (see Figure 15.1).

Figure 15.1 Simple page view counter.

This page has one important problem, however. Suppose that the current value of PageCounter is 590. Now imagine that two people, named Fred and Jane, request the page at the same time. When both Fred and Jane request the page, PageCounter has the value 590. When Fred's request for the page finishes processing, the value of PageCounter is incremented by one so that it has the new value 591. When Jane's request for the page finishes processing, the value of PageCounter is incremented by one so that it has the new value 591. See the problem?

Because items stored in application state are shared among multiple pages, conflicts can occur. In this simple case, the worst outcome is inaccurate data. However, when users are working with more complicated objects in application state—such as collections—concurrent access to items stored in application state can result in deadlocks, race conditions, and access violations. In other words, storing items in application state can cause your application to crash in a very messy manner.

Fortunately, you can find ways to get around this problem. You can force access to items in application state to take place in an orderly manner by using two methods of the application state object: Lock and Unlock.

The Lock method locks the application state object so that it can be accessed only by the current thread. Typically, this means that only the current page can access objects in application state.

The Unlock method releases the lock on application state. It enables other pages to access values in application state again.

NOTE

You should always unlock the application state object as quickly as possible. However, if you do forget to call Unlock after Lock, the lock is automatically released when the current request finishes processing, an error occurs, or the request times out.

The page in Listing 15.2 illustrates how you can use the Lock and Unlock methods.

Listing 15.2 GoodPageCounter.aspx

<Script Runat="Server">

Sub Page_Load
 Application.Lock
 Application( "PageCounter" ) += 1
 lblCount.Text = Application( "pageCounter" )
 Application.Unlock
End Sub

</Script>

<html>
<head><title>GoodPageCounter.aspx</title></head>
<body>

This page has been requested:
<asp:Label
 ID="lblCount"
 Runat="Server" />
times!

</body>
</html>

In Listing 15.2, the Lock method is called immediately before PageCounter is updated. The Unlock method is called immediately after PageCounter is assigned to the Label control.

Notice that the Lock method locks the application state object as a whole. You cannot selectively lock items in application state. Because locking application state blocks all other access to any item, using application state unwisely in a high volume Web site can have a serious impact on the performance of the site.

You must be cautious about using complex objects, such as collections, in application state. Collections such as ArrayLists and HashTables are not designed to be accessed by multiple threads at the same time. This means that you should either use the Lock and Unlock methods whenever accessing collections in application state, or you should create thread-safe versions of the collections.

You can create thread-safe versions of the collection objects by using the Synchronized() method, which returns a thread-safe wrapper for the collection. For example, the following statements return a thread-safe instance of an ArrayList:

Dim colArrayList As ArrayList

colArrayList = New ArrayList
colArrayList.Add( "Plato" )
colArrayList.Add( "Frege" )
colArrayList.Add( "Carnap" )
colArrayList = ArrayList.Synchronized( colArrayList )

In this example, a new ArrayList containing three items is created. When first created, the ArrayList is not thread-safe. However, in the final statement, the Synchronized method is used to return a thread-safe wrapper for the ArrayList.

Using the Global.asax File

Every ASP.NET application can contain a single file named Global.asax in its root directory. This special file can be used to handle application wide events and declare application wide objects.

WARNING

The Global.asax file is not used to display content. If you request this file, you receive an error. This error is intentional. Enabling users to view the contents of the Global.asax file would constitute a serious security hole.

Every ASP.NET application supports a certain number of events. The following is a list of the most important of these events:

  • Application_AuthenticateRequest—Raised before authenticating a user.

  • Application_AuthorizeRequest—Raised before authorizing a user.

  • Application_BeginRequest—Raised by every request to the server.

  • Application_End—Raised immediately before the end of all application instances.

  • Application_EndRequest—Raised at the end of every request to the server.

  • Application_Error—Raised by an unhandled error in the application.

  • Application_PreSendRequestContent—Raised before sending content to the browser.

  • Application_PreSendRequestHeaders—Raised before sending headers to the browser.

  • Application_Start—Raised immediately after the first application is created. This event is guaranteed to occur only once.

  • Dispose—Raised immediately before the end of a single application instance.

  • Init—Raised immediately after each application instance is created. This event might occur multiple times.

This list contains the standard application events. If you add other modules to your application, additional events are exposed in the Global.asax file. For example, FormsAuthenticationModule exposes a Forms_Authenticate event, and SessionStateModule exposes both Session_Start and Session_End events.

In the section titled "Using HTTP Handlers and Modules" later in this chapter, you learn how to add your own modules to the Global.asax file.

Classic ASP

The Global.asax file is backward-compatible with the Global.asa file. To retain compatibility with the Global.asa file, you can refer to events by using the syntax Application_OnEventName. For example, you can use either Application_Start or Application_OnStart in the Global.asax file.

You can handle any of these events in the Global.asax file by adding the appropriate subroutine. In general, the subroutine should look like this:

Sub Application_EventName
 ... application code
End Sub

Modifying the Global.asax file restarts the application. Any information stored in application (or session) state is lost. So, be cautious about modifying the Global.asax file on a production Web site.

Understanding Context and Using the Global.asax File

Within an ASP.NET page, the Page object is the default object. In many cases, if you do not specify an object when you call a method or access a property, you are implicitly calling the method or accessing the property from the Page object. For example, when you call the MapPath method (which maps virtual paths to physical paths), you are actually calling the Page.MapPath method. Or, when you access the Cache property, you are implicitly accessing the Page.Cache property.

The Global.asax file does not have the Page object as its default object. This means that you cannot simply call a method such as MapPath and expect it to work. Fortunately, in most cases, you can use a simple workaround. Instead of calling the MapPath method, you can call the Context.MapPath method. Or, instead of accessing the Page.Cache object, you can access the Context.Cache object.

If a familiar property or method does not work in the Global.asax file, you should immediately try calling the method or property by using the Context object instead.

Handling the Application Start and Init Events

Imagine that you have just plugged your Web server into a power outlet and the Web server has booted up. When the first user makes a request to your Web site, the Application_Start event occurs.

The Application_Start event is guaranteed to occur only once throughout the lifetime of the application. It's a good place to initialize global variables. For example, you might want to retrieve a list of products from a database table and place the list in application state or the Cache object.

NOTE

To learn more details about the Cache object, see Chapter 17, "Caching ASP.NET Applications."

The page in Listing 15.3 illustrates how you can add the contents of the Products database table to the Cache object within a subroutine that handles the Application_Start event.

Listing 15.3 AppCache/Global.asax

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Application_Start
 Dim conNorthwind As SqlConnection
 Dim strSelect As String
 Dim dadProducts As SqlDataAdapter
 Dim dstProducts As DataSet

 conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
 strSelect = "Select * From Products"
 dadProducts = New SqlDataAdapter( strSelect, conNorthwind )
 dstProducts = New DataSet()
 dadProducts.Fill( dstProducts, "Products" )
 Context.Cache( "Products" ) = dstProducts
End Sub

</Script>

NOTE

Within this Global.asax file, you must use Context.Cache, rather than Cache, because the Page object is no longer the default object.

You can find the Global.asax file in Listing 15.3 in the AppCache subdirectory on the CD. To test how the page works, you can open the DisplayProducts.aspx page, which displays the list of products added to the Cache object (see Figure 15.2).

Figure 15.2 Displaying Cached products.

The ASP.NET framework uses a pool of application instances to process each request to the server. When a request is made, an application instance is assigned to the request. Immediately after any of these application instances are created, the Init event is raised.

Requesting the first page from the Web site raises both the Application_Start and Init events. The Application_Start event won't be raised again for the lifetime of the application. The Init event, on the other hand, might be raised multiple times.

You can use the Init event to initialize any variables or objects that you'll need to use throughout the lifetime of a particular application instance. If you assign values to local variables, the variables retain their values across multiple requests.

The page in Listing 15.4, for example, illustrates how you can automatically add a random banner advertisement to the bottom of every page (see Figure 15.3).

Figure 15.3 Displaying random banner advertisement.

Listing 15.4 AppInit/Global.asax

<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Dim dtblBannerAds As DataTable

Overrides Sub Init()
 Dim dstBannerAds As DataSet

 dstBannerAds = New DataSet
 dstBannerAds.ReadXml( Server.MapPath( "adFile.xml" ) )
 dtblBannerAds = dstBannerAds.Tables( 0 )
End Sub

Sub Application_PreSendRequestContent
 Dim strPageFooter As String
 Dim objRan As Random
 Dim drowSelectedAd As DataRow

 objRan = New Random()
 drowSelectedAd = dtblBannerAds.Rows( _
  objRan.Next( dtblBannerAds.Rows.Count ) )
 strPageFooter = "<hr><a href=" & drowSelectedAd( "NavigateUrl" )
 strPageFooter &= "><img src=" & drowSelectedAd( "ImageUrl" )
 strPageFooter &= "></a>"
 Response.Write( strPageFooter )
End Sub
</Script>

You can find the Global.asax file in Listing 15.4 in the AppInit subdirectory on the CD that accompanies this book. You can open the page named DisplayAd.aspx to see how a banner advertisement is automatically appended to every page.

In the Init subroutine in Listing 15.4, a list of banner advertisements is loaded from an XML file named adFile.xml into a DataSet. This subroutine is executed only once for each application instance.

Next, within the Application_PreSendRequestContent subroutine, one advertisement is randomly selected from the DataSet and sent to the browser. The PreSendRequestContent subroutine is executed just before content being sent to the browser. Therefore, outputting content within this subroutine guarantees that it will be displayed at the bottom of the page.

Notice that initializing variables in the Init subroutine is similar to adding items to application state. The crucial difference is that items added to application state are guaranteed to survive across multiple application instances, whereas variables created in the Init subroutine survive only one application instance.

Handling the Application_BeginRequest Event

The Application_BeginRequest event is raised when the request starts to be processed. You can exploit this event in several ways. Suppose, for example, that you want to create vanity URLs at your Web site. You want registered users to be able to enter a URL, such as http://www.superexpert.com/swalther, and view a page customized only for them.

You would not, however, want to manually create a new page for each user. Instead, you would want to secretly transfer the user to a single page that handles all user requests for the page. In that case, you can capture the Application_BeginRequest event and automatically transfer the user to the new page by using the RewritePath method. The Global.asax page in Listing 15.5 demonstrates how you can use the RewritePath method with the Application_BeginRequest event. (You can find this file in the AppVanity subdirectory on the CD that accompanies this book.)

Listing 15.5 AppVanity/Global.asax

<Script Runat="Server">

Sub Application_BeginRequest
 Dim strUsername
 Dim strCustomPath As String

 If INSTR( Request.Path.ToLower, "custom" ) = 0 Then
  strUsername = Request.Path.ToLower
  strUsername = strUsername.Replace( ".aspx", "" )
  strUsername = strUsername.Remove( _
   0, _
   InstrRev( strUsername, "/" ) )
  If Request.ApplicationPath = "/" Then
  strCustomPath = _
   String.Format( _
    "/custom/default.aspx?username={0}", _
    strUsername )
  Else
  strCustomPath = _
   String.Format( _
    "{0}/custom/default.aspx?username={1}", _
    Request.ApplicationPath, _
    strUsername )
  End If 
  Context.RewritePath( strCustomPath )
 End If
End Sub

</Script>

The Global.asax file in Listing 15.5 contains an Application_BeginRequest subroutine. This subroutine checks whether the current URL contains the name of the custom subdirectory in its path. If not, the beginning and end of the URL are stripped, and the RewritePath method transfers the user to the /custom/default.aspx page.

If you enter the URL http://superexpert.com/swalther.aspx in the browser address bar, it is automatically rewritten like this:

http://superexpert.com/custom/default.aspx?username=swalther

Within the Default.aspx file in the custom subdirectory, you can grab the username query string and customize the page for the particular user. The page in Listing 15.6 simply displays the username in the body of the page. (You can find this page in the /AppVanity/Custom subdirectory on the CD that accompanies this book.)

Listing 15.6 AppVanity/Custom/Default.aspx

<Script Runat="Server">

Dim strUsername As String

Sub Page_Load
 strUsername = Request.Params( "username" )
End Sub  

</Script>

<html>
<head><title>Welcome!</title></head>
<body>

Hello <b><%=strUsername%></b> and welcome to your Web site! 

</body>
</html>

The page in Listing 15.6 retrieves the username parameter from the query string and displays it (see Figure 15.4). A more complicated page might grab data from the database associated with the particular user and display the information.

Figure 15.4 A vanity page.

You also could use the RewritePath method with the Application_BeginRequest event to fix broken links. For example, you could create an XML file that maps bad links to good links. Say that your Web site included products and services pages in the past, but now you have removed them and you want to redirect all users to the default page. The XML file in Listing 15.7 illustrates how you could create this XML file. (You can find this file in the AppFixLinks subdirectory on the CD that accompanies this book.)

Listing 15.7 AppFixLinks/BadLinks.xml

<badlinks>
 <link>
  <badlink>/products.aspx</badlink>
  <goodlink>/default.aspx</goodlink>
 </link>
 <link>
  <badlink>/services.aspx</badlink>
  <goodlink>/default.aspx</goodlink>
 </link>
</badlinks>

The XML file in Listing 15.7 maps the URLs /products.aspx and /services.aspx to the URL /default.aspx.

The Global.asax file in Listing 15.8 uses the RewritePath() method to automatically redirect users from bad links to good links.

Listing 15.8 AppFixLinks/Global.asax

<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Application_BeginRequest
 Dim dtblBadLinks As DataTable
 Dim strThisUrl As String
 Dim strSelect As String
 Dim arrMatches() As DataRow
 Dim strGoodLink As String

 dtblBadLinks = GetBadLinks()
 strThisUrl = Request.Path.ToLower()
 If Request.ApplicationPath <> "/" Then
  strThisUrl = strThisUrl.Remove( 0, Request.ApplicationPath.Length )
 End If
 strSelect = "badlink='" & strThisURL & "'"
 arrMatches = dtblBadLinks.Select( strSelect, "badlink" )
 If arrMatches.Length > 0 Then
  strGoodLink = arrMatches( 0 )( "goodlink" )
  strGoodLink = Request.ApplicationPath & strGoodLink
  Context.RewritePath( strGoodLink )
 End If
End Sub

Function GetBadLinks() As DataTable
 Dim dstBadLinks As DataSet
 Dim dtblBadLinks As DataTable

 dtblBadLinks = Context.Cache( "badlinks" )
 If dtblBadLinks Is Nothing Then
  dstBadLinks = New DataSet
  dstBadLinks.ReadXml( Server.MapPath( "badlinks.xml" ) )
  dtblBadLinks = dstBadLinks.Tables( 0 )
  Context.Cache.Insert( "badlinks", _
   dtblBadLinks, _
   New CacheDependency( Server.MapPath( "badlinks.xml" ) ) )
 End If
 Return dtblBadLinks
End Function

</Script>   

The Badlinks.xml file is retrieved in the GetBadLinks() function. If the Badlinks.xml file is not already cached in memory, it is added to the Cache object. (The Badlinks.xml file is inserted with a Cache file dependency so that the Cache object is automatically updated if the Badlinks.xml file is modified.)

NOTE

To learn more about creating file dependencies see Chapter 17, "Caching ASP.NET Applications."

In the Application_BeginRequest subroutine, the path of the current URL is checked against the list of bad links contained in the Badlinks.xml file. If a match is made, the RewritePath() method automatically transfers the user from the bad link to the good link.

If a user enters the address http://superexpert.com/products.aspx in a Web browser, he or she is automatically transferred to the following new page specified in the Badlinks.xml file:

http://superexpert.com/default.aspx

You can use this method to fix any bad links on your Web site. All you need to do is maintain the Badlinks.xml file.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020