Maintaining state

One of the biggest drawbacks of the earlier example is that it fetches the full set of results at one time, which is impractical for large result sets. It is more practical to display a full set of results across multiple pages and allow the user to move forward or backward through the pages.

This is simple in a conventional application where a connection to the separate server is maintained until the user terminates the application. In a web implementation however, this seemingly simple requirement involves a considerably higher level of complexity due to the stateless nature of web pages. One such complexity is that each time a new page of results is displayed, the initial search for the records must be re-executed. This is inconvenient for the web programmer and potentially slow for the user.

The IMu server provides a solution to this. When a handler object is created, a corresponding object is created on the server to service the handler's request: this server-side object is allocated a unique identifier by the IMu server. When making a request for more information, the unique identifier can be used to connect a new handler to the same server-side object, with its state intact.

The following example illustrates the connection of a second, independently created Module object to the same server-side object:

// Create a module object as usual
Module first = new Module("eparties", session);
// Run a search - this will create a server-side object
int[] keys = { 1, 2, 3, 4, 5, 42 };
first.FindKeys(keys);
// Get a set of results
ModuleFetchResult result1 = first.Fetch("start", 0, 2, "SummaryData");
// Create a second module object
Module second = new Module("eparties", session);
/*
 * Attach it to the same server-side object as the first module.
 * This is the key step.
 */
second.ID = first.ID;
// Get a second set of results from the same search
ModuleFetchResult result2 = second.Fetch("current", 1, 2, "SummaryData");

Although two completely separate Module objects have been created, they are each connected to the same server-side object by virtue of having the same ID property. This means that the second Fetch call will access the same result set as the first Fetch. Notice that a flag of current has been passed to the second call. The current state is maintained on the server-side object, so in this case the second call to Fetch will return the third and fourth records in the result set.

While this example illustrates the use of the ID property, it is not particularly realistic as it is unlikely that two distinct objects which refer to the same server-side object would be required in the same piece of code. The need to re-connect to the same server-side object when generating another page of results is far more likely. This situation involves creating a server-side Module object (to search the module and deliver the first set of results) in one request and then re-connecting to the same server-side object (to fetch a second set of results) in a second request. As before, this is achieved by assigning the same identifier to the ID property of the object in the second page, but two other things need to be considered.

By default the IMu server destroys all server-side objects when a session finishes. This means that unless the server is explicitly instructed not to do so, the server-side object may be destroyed when the connection from the first page is closed. Telling the server to maintain the server-side object only requires that the Destroy property on the object is set to false before calling any of its methods. In the example above, the server would be instructed not to destroy the object as follows:

C#

Module module = new Module("eparties", session);

module.SetDestroy(false);

int[] keys = { 1, 2, 3, 4, 5, 42 };

module.FindKeys(keys);

VB

 

The second point is quite subtle. When a connection is established to a server, it is necessary to specify the port to connect to. Depending on how the server has been configured, there may be more than one server process listening for connections on this port. Your program has no control over which of these processes will actually accept the connection and handle requests. Normally this makes no difference, but when trying to maintain state by re-connecting to a pre-existing server-side object, it is a problem.

For example, suppose there are three separate server processes listening for connections. When the first request is executed it connects, effectively at random, to the first process. This process responds to the request, creates a server-side object, searches the Parties module for the terms provided and returns the first set of results. The server is told not to destroy the object and passes the server-side identifier to another page which fetches the next set of results from the same search.

The problem comes when the next page connects to the server again. When the connection is established any one of the three server processes may accept the connection. However, only the first process is maintaining the relevant server-side object. If the second or third process accepts the connection, the object will not be found.

The solution to this problem is relatively straightforward. Before the first request closes the connection to its server, it must notify the server that subsequent requests need to connect explicitly to that process. This is achieved by setting the Session object's Suspend property to true prior to submitting any request to the server:

C#

Session session = new Session("server.com", 12345);

Module module = new Module("eparties", session);

session.SetSuspend(true);

module.FindKeys(keys);

VB

 

The server handles a request to suspend a connection by starting to listen for connections on a second port. Unlike the primary port, this port is guaranteed to be used only by that particular server process. This means that a subsequent page can reconnect to a server on this second port and be guaranteed of connecting to the same server process. This in turn means that any saved server-side object will be accessible via its identifier. After the request has returned (in this example it was a call to FindKeys), the Session object's Port property holds the port number to reconnect to:

C#

session.SetSuspend(true);

module.FindKeys(keys);

int reconnect = session.Port;

VB

 

Example

To illustrate we'll modify the very simple results page of the earlier section to display the list of matching names in blocks of five records per page. For the purpose of demonstration we'll provide simple Next and Previous mechanic to allow the user to move through the results, and we'll remember the port we want to reconnect to, the identifier of the server-side object and the rownum of the first record to be displayed.

  1. Create a Session object with parameters Host and Port, then establish a connection and immediately set the Suspend property to true to tell the server that we may want to connect again:

    Session session = new Session(Host, Port);

    session.Connect();

    session.Suspend = true;

    This ensures the server listens on a new, unique port.

  2. Create the client-side Module object and set its Destroy property to false:

    Module parties = new Module("eparties", session);

    parties.Destroy = false;

    This ensures that the server will not destroy the corresponding server-side object when the session ends.

  3. If we have search terms, we will perform a new search. If connecting to a new server-side module we record the current ModuleID property, otherwise we set the ID property:

    parties.Destroy = false;

    if (ID == null)

    ID = parties.ID;

    else

    parties.ID = ID;

  4. If we have no search terms but want to fetch from an existing set of results, we simply set the Module ID:

    parties.ID = ID;

  5. Build a list of columns to fetch:

    static private String[] columns =

    {

    "NamFirst",

    "NamLast"

    };

  6. If the function parameters included a rownum parameter, fetch records starting from there. Otherwise we set a default rownum value of 1 and start from there:

    parties.ID = ID;

    After this, we display the results. For the sake of our console example, we manually disconnect from the server, as we do not need to 'refresh' like a web browser.

  7. Finally, we allow the user to move forwards and backwards through the results. To do this we need to pass on the information about our connection, current position and number of hits.

    Map value = new Map();

    value.Add("ID", parties.ID);

    value.Add("Hits", results.Hits);

    value.Add("rownum", rownum);

    return value;

    We then start a loop, prompting the user to move forward / backwards between results or to quit:

    input = Console.ReadKey();

    if (input.Key == ConsoleKey.Q)

    break;

    if (input.Key == ConsoleKey.LeftArrow)

    {

    rownum -= count;

    if (rownum < 1)

       rownum = 1;

    }

    else if (input.Key == ConsoleKey.RightArrow)

    {

    if (rownum + count < Hits)

       rownum += count;

    }

    else

    continue;

    result = ServerRequest(ID, rownum);

    rownum = result.GetInt("rownum");

    Hits = result.GetLong("Hits");