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.
- Create a
Session
object with parametersHost
andPort
, then establish a connection and immediately set theSuspend
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.
- Create the client-side
Module
object and set itsDestroy
property tofalse
: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.
- If we have search terms, we will perform a new search.
If connecting to a new server-side module we record the current
Module
ID
property, otherwise we set theID
property:parties.Destroy = false;
if (ID == null)
ID = parties.ID;
else
parties.ID = ID;
- 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;
- Build a list of columns to fetch:
static private String[] columns =
{
"NamFirst",
"NamLast"
};
- If the function parameters included a
rownum
parameter, fetch records starting from there. Otherwise we set a defaultrownum
value of1
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.
- 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");