Home > Articles > Programming > Windows Programming

  • Print
  • + Share This
This chapter is from the book

Getting to the Controller

I like to follow this basic design tenet when designing web services:

Move as much processing logic out of the web service as possible.

In other words, when an HTTP request comes into the web service, do as little processing in the web service itself as possible, and farm out all remaining logic to classes in "the engine." In this case, the engine begins with a class called Controller. Aside from basic XML structural validation and user/session authorization, the vast bulk of the processing occurs in or below this Controller class.

The reason for this approach was born out of experience. In the early days of the .NET web service classes, I (and others) had trouble putting complex logic in the web service classes. To be fair, I also ran into problems with the Java Servlet components. Various combinations of "stuff" caused the process to hang. In other cases, the results were not what I had expected, and the debugging task was difficult. As a matter of habit, then, I have come to move the more interesting logic out of the web service itself and to call helper classes instead.

The Controller component is an example of a "point man" class—a class acting as the lead in an entire chain of other classes—used to kick off the bulk of the actual processing. This tends to make the code easier to read anyway and greatly reduces the debugging complexity, because you then can write standalone tester programs that call this class.

The issues I noted previously with the web service classes likely have been fixed in the .NET Framework. However, I will continue to use this approach of moving logic out, because doing so brings clarity and easier debugging.

Figure 7-1 is a rather high-level sequence diagram of a request flow. It shows the calling sequence from the initiation of a request through the calling of the Controller component. This should put into perspective the context of the remainder of this section, in which we discuss the Controller in more detail.

Figure 7.1Figure 7-1 Getting to the Controller.

The Controller Structure

Surprisingly, the Controller class is named Controller. It takes in as its sole parameter an XmlNode object and returns an EAIResponse object. By the time the Controller gets the go-ahead to start processing the input via its process method, you should know a few things:

  • The input request was a well-formed XML message.

  • If the request was a new login message, and the username and password were valid.

  • If the request was submitted for an existing session, that session was valid.


The Controller doesn't actually see login requests. They are handled by the web service itself.

Because we know each of these pieces of information here, it's full steam ahead as we enter the process method. The XmlNode object is simply a representation of the request string the originator submitted. We expect this to be in the following format:


  <Request Name="DoThis">

The Controller class has a default, blank constructor. It then has a single public method, named process.We look at a couple of private helper methods in a moment.

From a high level, the Controller component is really responsible for only a few, albeit important tasks:

  • Converting an XmlNode object into an EAIRequest object

  • Determining whether the request is synchronous or asynchronous

  • Sending an EAIRequest object to MessageQueue for asynchronous requests

  • Creating a RequestsProcessor object for synchronous requests

  • Firing off the actual Requests to the RequestsProcessor

  • Compiling status info and creating and returning an EAIResponse object

Figure 7-2 is a sequence diagram of a simple synchronous request coming into the Controller.

Figure 7.2Figure 7-2 Controller synchronous request processing.

The Controller first creates a new EAIResponse object that will be returned from the process call. It then converts the incoming XmlNode object, representing the original request, into an EAIRequest object. This is a helper object that contains various information and objects that make it much easier to process the request. Among the EAIRequest members is a Requests object that contains Request objects. These Request objects each represent a single <Request> block from the incoming request.

After the EAIRequest object has been created, Controller checks to see if the request is synchronous or asynchronous. For our purposes, a synchronous request is specified in the incoming message by the originator, and it means that the caller expects the request to be handled while he waits. In other words, the caller expects a response to the request that contains status information about all Request block processing for requests sent in. For example, a request specified for asynchronous processing would have the following structure:

 <Requests Asynch="true">
   <Request Name="First"> ... </Request>
   <Request Name="Last"> ... </Request>

To make matters a bit more confusing (but flexible), the caller can also specify that he wants processing to either continue on in the face of Requests returning an error or to FailOnFirstError. Failing on first error means that processing stops the first time a Request block returns an error. This is accomplished by the optional XML attribute FailOnFirstError on the Requests element. If the attribute is not present, FailOnFirstError is set to false. For example, if you were submitting a bunch of Request blocks in a Requests block and you wanted the processing to halt the first time any Request returned an error, you would send in something like the following:

 <Requests FailOnFirstError="true">
   <Request Name="First"> ... </Request>
   <Request Name="Last"> ... </Request>

Therefore, you have four ways in which to combine the preceding two attributes. They are shown in Table 7-1.

Table 7-1 FailOnFirstError and Asynch request attributes


FailOnFirstError = TRUE

FailOnFirstError = FALSE


Processing starts from Controller and returns when the first error is encountered processing a Request block. Therefore, not all requests might even have processing attempted.

Processing starts from Controller and continues on with each Request block being processed, even if one or more Request blocks return an error.


Processing starts from the Microsoft Message Queuing (MSMQ) and returns when the first error is encountered processing a Request block. Therefore, not all requests might even have processing attempted.

Processing starts from the MSMQ and continues with each Request block being processed, even if one or more Request blocks return an error.

Listing 7-1: Controller.cs DiscussionController.cs

using System;
using System.Xml;
using System.Messaging;
using System.Collections;

using EAIFramework.Messages;
using EAIFramework.Util;
using EAIFramework.Handler;

namespace EAIFramework.Controller
 /// <summary>
 /// EAIFramework.Controller.Controller
 ///  This class is responsible for taking in an XmlNode object
 ///  and converting it to an EAIRequest object. Then each
 ///  Request block in the incoming EAIRequest message is
 ///  processed in either a synchronous or asynchronous
 ///  fashion, as specified in the incoming message.
 /// </summary>
 public class Controller
   public Controller(){}//end constructor

Listing 7-2 shows the process method that will do the bulk of the work.

Listing 7-2: Controller.cs process Method

  public EAIResponse process(XmlNode req)
   Logger.log(Logger.INFO,"Entering Controller ");
   Logger.log(Logger.INFO,"with str: " +req.OuterXml);
   TrnxLogProxy tlp = new TrnxLogProxy();

   EAIFramework.Messages.EAIResponse resp =
    new EAIFramework.Messages.EAIResponse();
   resp.OverallStatus = StatusCodes.Descriptions(

   // Create the EAIRequest object
    "Controller: Going to create new EAIRequest");
   EAIRequest eaiReq =
   if( ! eaiReq.Synchronous) 
    resp = this.processAsynchRequests(eaiReq);
    // Now save and return the response to the caller

    int nRows = tlp.SaveResponse(resp.ToString(),
    return resp;
   }//end Asynch request.

   // The rest of this method is handling 
   //  a SYNCHRONOUS request
   // Set up a place to compile the results of
   // the <Request> processing. There will be
   // one Component (in Components) for each
   // Request (in Requests). This will come back in the
   // ArrayList rreqs, below.

   RequestsProcessor processor = new RequestsProcessor(
   ArrayList rreqs = processor.process();

   if( !(eaiReq.SessionID.Equals("")) )
    resp.RequestingUsername = eaiReq.SessionID;
   resp.RequestingUsername = 

   // Populate the EAIResponse object with 
   // the results of the processing
   // Obscure the Password, if it's there
    InnerText = "*****";}
   catch(Exception exc){}

   resp.RequestResponses = (RequestResponse[]) 
    new RequestResponse().GetType());
   int nRespRows = tlp.SaveResponse(resp.ToString(),
   return resp;
  }//end process()

The process method performs, or at least kicks off, the heavy lifting of Request processing. It starts off by doing some diagnostic logging and then creates a TrnxLogProxy object. This is used later to update the transaction in the database.

Next, a new EAIResponse object, named resp, is created that you use to return to the caller all the status information gathered from the various steps needed to process each Request block. By default, you set the response object as showing success (OK).

The first of the private helper methods, CreateRequest(), is called. It takes in the XmlNode object and returns a populated EAIRequest object. This object can then be used to easily gain access to the various parts of the input request. We take a look at CreateRequest() in a moment.

After CreateRequest() is called, the main branching for synchronous or asynchronous processing takes place. If the request is not to be handled in a synchronous manner (that is, if it's asynchronous), you call the private helper method processAsynchRequests(). This also is discussed in a moment. This basically sends the request to a message queue that watches for asynchronous requests. Before you leave, you save off the response to the database.

On the other hand, if this request is to be handled in a synchronous manner, a new RequestsProcessor object is created. The RequestsProcessor class, discussed later in this chapter, encapsulates all the functionality necessary to actually process a bunch of requests. You could have written the code for this processing here in the Controller class. However, because the request is sent off to a message queue for asynchronous processing, you can call the same RequestsProcessor class from both the Controller class and the message queue. This leaves you with a single chunk of code, making maintenance and enhancements easier and more reliable.

The process method of the RequestsProcessor class is called next, and you get back an ArrayList of RequestResponse objects. There should be one for each processed Request. When FailOnFirstError is false, you should see a RequestResponse object for each Request. If FailOnFirstError is true, you might see fewer RequestResponse objects than Requests, if one of the Requests fails.

When the RequestsProcessor returns its results, Controller updates the EAIResponse object that will be sent back. You'll recall that the T_TransactionLog table has a column to hold either SubmittingUser or SessionID, because both will never come in at the same time. Controller sets the RequestingUsername member to the value of either a SubmittingUsername or a SessionID, whichever is present. It then sets the Password field to a series of asterisks, if password has a value. This just masks the password in the response message. It's true that, for now, you should never see requests with a username and password hit the Controller. However, to be prepared for a time when you need to send login requests to the Controller, the information will be presented back to the user as he would expect it to be. Finally, the response object is populated with the remainder of the interesting information, including the TransactionID, SessionID, OriginalXML string, and RequestResponse status objects returned by the RequestsProcessor.

To finish out the Controller.process method, the TrnxLogProxy object that was created near the start of the method is called on two different methods. First, the UpdateTransaction() method is called. This takes in the newly-created response object and does an UPDATE command on the row in the T_TransactionLog table that has the TransactionID held in the response object. It at least changes the Status and StatusCode columns.

All that's left to do is return the EAIResponse object to the caller. For now, the only component calling the Controller class is the web service. It then returns to the original caller a string representation of the response object that you just sent back.

Private Helper Methods

Now let's take a look at the private helper methods in the Controller class that the process method called. The longest of these methods is the CreateRequest() method, shown in Listing 7-3. It takes in an XmlNode object and returns an EAIRequest object.

Listing 7-3: The CreateRequest() Method

  /// <summary>
  /// This is a helper method for the
  /// process() method that hides all
  /// the logic to build the EAIRequest
  /// object from the input XmlNode
  /// object
  /// </summary>
  /// <param name="req">The input XmlNode object</param>
  /// <returns>EAIRequest object</returns>
  private EAIRequest CreateRequest(XmlNode req)
   EAIRequest rRet = new EAIRequest();

   rRet.OriginalXML = req.OuterXml;
   try {
    rRet.RequestingUsername = 
   } catch (Exception exc) {
     "CreateReq: Couldn't read RequestingUsername: "
     + exc.Message);

    rRet.TransactionID = 
   } catch (Exception exc) {
      "CreateReq: Couldn't read TrnxID: " +
    rRet.TransactionID = -1;

    rRet.OverallStatus =
   } catch (Exception exc) {
      "CreateReq: Couldn't read OverallStatus: " +
   try {
      "CreateReq: Going to get StatusCode now...");
    string sSC = req.SelectSingleNode(
    if( (sSC==null) || sSC.Equals(""))
     "CreateReq: Just got StatusCode: " + sSC);
    int nSC = Int32.Parse( sSC );
     "CreateReq: Just converted int to: " + 
    rRet.OverallStatusCode = nSC;
     "CreateReq: Stuck in OverallStatusCode.");
   } catch(Exception exc) {
     "CreateReq: Couldn't read OverallStatusCode: "
     + exc.Message);
   }//end catch

   try {
     "CreateReq: Going to get SessionID now...");
    string ssn = req.SelectSingleNode(
    rRet.SessionID = ssn;
     "CreateReq: Stuck in SessionID " + ssn);
   } catch(Exception exc) {
     "CreateReq: Couldn't read SessionID: " + 
    rRet.SessionID = "";
   }//end catch
   //rRet.Started =
   // new DateTime( req.SelectSingleNode(
   //  "/EAIRequest/Started").InnerText);
   XmlNode reqs = req.SelectSingleNode(
   XmlAttribute async = reqs.Attributes["Asynch"];   
   if(async != null) {
     rRet.Synchronous = false;
     rRet.Synchronous = true;
   }//End if Asynch != null
    rRet.Synchronous = true;

   XmlAttribute fof = reqs.
   if(fof != null) {
     rRet.FailOnFirstError = true;
     rRet.FailOnFirstError = false;
   }//End if FailOnFirstError != null
    rRet.FailOnFirstError = false;

   // Now build the Requests object
   XmlNodeList xnl = req.SelectNodes(
   Requests r = new Requests();
    int nReqNum = 0;
    foreach(XmlNode xn in xnl)
     Request rr = new Request(xn.OuterXml);
     rr.TrnxID = rRet.TransactionID;
     rr.Iteration = nReqNum++;
    }//end foreach
    rRet.Requests = r;
   }//end if there are any requests
   rRet.Requests = r;

   return rRet;
  }//end CreateRequest()

The CreateRequest() method begins by instantiating a new EAIRequest that will be returned to the caller. It then steps through, member by member; pulls out the information from the XmlNode object sent in; and populates the appropriate member of the request object. Working with the XML is the same as done previously, with the possible exception of pulling in an attribute. You will want to check for a couple of different attributes, including FailOnFirstError and Asynch.

I will now walk through pulling out a particular XML element, to make sure you understand what is going on in the code. You'll notice that very similar code repeats several times, getting different XML elements. I will then walk through pulling out an XML attribute. It also repeats for subsequent attributes later in the method.

First, let's look at the code to pull out the RequestingUsername element (see Listing 7-4). The statement is placed in a try/catch block, because the SelectSingleNode() method can throw an exception. The code populates the RequestingUsername member of the rRet (EAIRequest) object, which is a string. It is populated with the InnerText—in other words, the value between the XML element tags—for the element matching the XPath search string of /EAIRequest/RequestingUsername. Let's take a look at the code that actually does the XPath search now.

Listing 7-4: XPath Example

 // First get the XmlNode object for the XPath search
 // of "/EAIRequest/RequestingUsername".
 // This XPath search should find the XML Element at the 
 // following spot:
 // <EAIRequest>
 //  <RequestingUsername>theUser</RequestingUsername>
 //   ...other interesting XML goodies here...
 //  </EAIRequest>

 System.XML.XmlNode xnReqUser = req.SelectSingleNode(

 // Now that we have the XmlNode for the RequestingUsername,

 // get the value for that element. In this case, strUser
 // would be set to equal "theUser".
 string strUser = xnReqUser.InnerText;

 // Finally, populate the RequestingUsername member with the

 // value we got for strUser.
 rRet.RequestingUsername = strUser;
catch( Exception exc)
  // error processing here...



As you might notice with most of the code in this project, I like to split most operations into separate statements. Over the years, I have found that it's much quicker to debug problems if the statements are separated than if you have many methods chained together in a single statement. I also tend to break that rule if I have become particularly comfortable with a series of calls. Crunching through an XmlNode is just one of those occasions. I will also concede that code looks "cooler" if it's all smushed into one line, but I long ago (and quite happily, I might add) traded worrying about looking cool for simplifying my life. Splitting multiple calls into separate lines is one such simplification.

The catch block traps any errors, including the case in which the element doesn't exist. If this is the case, the response RequestingUsername is set to a blank string. The same holds true for several other bits of information, including the TransactionID, OverallStatus, OverallStatusCode, and so on. Each of these works the same way.

Midway through this method is the code to pull out the Request XML nodes. Here is where you hit the first of the XML attributes, Asynch. You tell the system to process this transaction asynchronously by sending in the attribute Asynch with a value of true on the Requests XML element. For example, the tag would look like this:

...  <Requests Asynch="true">...</Requests>...

The default is to handle requests synchronously so that if the attribute is left off or is set to a value of false, you set the EAIRequest member Synchronous to true. Otherwise, you set the Synchronous member to false, meaning that you want the incoming transaction to be processed in an asynchronous fashion.

To get the Asynch attribute, you first get the XmlNode for the Requests block by using the statement SelectSingleNode("/EAIRequest/Requests"), exactly as you did earlier for other pieces of information. The next statement, however, is where you create an XmlAttribute object. This is accomplished with the following statement:

XmlAttribute async = reqs.Attributes["Asynch"];

This is another case in which the .NET Framework enables you to specify an array item by name rather than by an index number. This is a very handy little trick that saves many lines of code by hiding the logic necessary to search through the array of items for a particular entry.

All you need to do now to get the value of the attribute is to look at the InnerText member, just like you did for XmlNode objects. Check to see if the InnerText equals true for the Asynch attribute. If so, set the rRet.Synchronous member to false, indicating that this transaction should be processed asynchronously. If the value sent in was not true for the Asynch attribute, set the rRet.Synchronous member to true. The other possibility for the Asynch attribute is that it wasn't sent in at all. The else block traps the situation in which the async (XmlAttribute) object is null. This means that the attribute wasn't sent in, so you set the rRet.Synchronous member to true, because synchronous processing is the default.

At this point, you immediately step into another section of code that checks for the FailOnFirstError attribute. It is processed in exactly the same manner as the Async attribute, but it sets the rRet.FailOnFirstError member. This member is used in the RequestsProcessor object to see whether to continue processing if a particular request returns something other than true.

The processAsynchRequests() Method

We end the discussion of the Controller class by taking a look at the processAsynchRequests() method. Until now, we have essentially ignored the asynchronous processing of transactions. Well, no more!

Asynchronous transaction processing occurs as follows:

  1. An EAIRequest object is created and sent to the processAsynchRequests() method.

  2. A Message object is created, along with a MessageQueue object.

  3. The Message is sent to the MessageQueue.

  4. If no errors are caught, meaning that the message was sent, a success EAIResponse object is returned to the caller.

  5. Otherwise, an error EAIResponse object is returned.

  6. Special code monitoring the MessageQueue takes the incoming request object and processes the individual Request blocks.

Figure 7-3 shows a sequence diagram of a successful asynchronous transaction flowing through the system. It is very similar to the synchronous transaction diagram in Figure 7-2, but instead of creating and calling an instance of RequestsProcessor, it uses a private helper method to send a Message object to a MessageQueue.

Figure 7.3Figure 7-3 Asynchronous request flow sequence diagram.

To keep it all together, we first look at the processAysnchRequests() method and then talk about using the message queues. You'll notice that creating a Message object and sending it to a message queue is quite easy and straightforward with .NET. You accomplish what previously was fairly complex processing with just a couple lines of code (see Listing 7-5).

Listing 7-5: processAsynchRequests() Method

  /// <summary>
  /// This method takes in an EAIRequest object and
  /// fires off the request to the RequestsQueueName
  /// MSMQ listener. It will then immediately return
  /// </summary>
  /// <param name="req"></param>
  /// <returns>EAIResponse</returns>
  protected EAIResponse processAsynchRequests(
   EAIRequest req)
   EAIResponse resp = new EAIResponse();
   resp.SessionID = req.SessionID;
   resp.TransactionID = req.TransactionID;
   ConfigData cdata = new ConfigData();
   string strQueName = cdata.getConfigSetting(

   MessageQueue mq;
    "Controller: Going to check que: " + 
    if( !MessageQueue.Exists(strQueName)){
    }//end if
   catch(Exception exc)
      "Controller: ERROR calling MessageQueue." +
     "Exists(): " + exc.Message);
   }//end catch
   mq = new System.Messaging.MessageQueue(strQueName);
   mq.DefaultPropertiesToSend.Recoverable = true;
   XmlMessageFormatter xmf = (XmlMessageFormatter)mq.Formatter;
   xmf.TargetTypes = new Type[]{typeof(EAIRequest)};
   mq.Formatter = xmf;

   System.Messaging.Message msg = new
    System.Messaging.Message( req );
   msg.Formatter = new XmlMessageFormatter(
     new Type[]{typeof(EAIRequest)});

    resp.OverallStatusCode = 1;
    resp.OverallStatus = StatusCodes.Descriptions(
    resp.Description = "Requests submitted to " + 
   }//end try
   catch(Exception exc)
    resp.OverallStatusCode = 50;
    resp.OverallStatus = StatusCodes.Descriptions(
    resp.Description = exc.Message;
   }//end catch

   return resp;
  }//end processAsynchRequests()

 }//end class
}//end namespace

Here's how that code breaks down. processAsynchRequests() takes in an EAIRequest object that was created earlier in the Controller. It returns an EAIResponse object, so the first thing it does is create an instance of this object and populate a few of the members.

The name of the message queue that will accept transaction requests—that is, the overall incoming request represented by the EAIRequest object—is identified in the config file by RequestsQueueName. Therefore, you create a ConfigData object, get the value for RequestsQueueName, and store it in a member called strQueueName.

As the first of the real message queue processing steps, you check to see if the particular message queue exists. If it doesn't, it is created. You do the check by calling the static method Exists() on the MessageQueue class.

The MessageQueue and Message classes are in the System.Messaging namespace. This namespace is kept in a library that must be added to your Visual Studio .NET project. In the Solution Explorer window in your project, right-click the References item and select Add Reference. On the .NET tab of the dialog box that pops up, scroll down to find the System.Messaging.dll entry. Select this and click Select. You'll see the System.Messaging entry appear in the Selected Components box at the bottom of the dialog box; click OK. You should then be put back in the VS.NET project. System.Messaging should be listed in the References section of the Solution Explorer.

Figure 7-4 shows the Add Reference dialog box, with the .NET library for System.Messaging selected. You can now include the Messaging namespace in your various components in the project with the following line:

using System.Messaging; 

Figure 7.4Figure 7-4 Add Reference dialog box.

Now you create a MessageQueue object for the queue and set the Formatter to handle our EAIRequest object (more on this in a moment). Finally, the message, which contains the EAIRequest object, is sent to the message queue for processing. If you don't get an error sending the message, the response object to be sent back to the caller is set with an OK status and is returned. If an error was encountered sending the message, you update the response object appropriately in the catch block and return the response object. It's important to keep in mind that the status, either OK or an error, is not reflective of the status of the request processing, in this case. It merely indicates whether the submission to the message queue was successful. The caller gets back the generated TransactionID in the response, which can be used later to query about the status of the actual processing.

Thus ends the asynch processing—well, okay, the sending of the request. The code to pick up the message and to process the requests is discussed later in this chapter. However, this is another area in which the .NET Framework has made very powerful functionality extremely easy to use. This is great for software developers, because they really don't have much code to write. It's not so great news for book authors hoping to make their code look impressive.

  • + Share This
  • 🔖 Save To Your Account

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.


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.


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.


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.


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


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


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.


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.


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