Introduction
Recently, I developed an ASP.NET Web Service that processes user requests and sends an e-mail for each request. The requests come from an internal desktop application with thousands of simultaneous users. The users are not aware of my Web Service, the service will be called as a result of the user performing certain tasks within the desktop application. I don't really know how many requests the Web Service has to process each day. The processing of requests involves retrieving data from the mainframe, which can take 4 to 20 seconds. Imagine that if over 100 requests come to my Web Service simultaneously, most of the users will have to wait a long time to get a response because the Web Service call from the desktop application is made synchronously.
To avoid any potential bottleneck, I decided to use a queue to store user requests for later processing. When a request comes in, my Web Service simply saves it into an internal queue and returns immediately so that users won't experience any visible delay. My Web Service has several threads running in the background that take user requests off the queue and process them.
Here is a new problem: if my Web Service needs to be shutdown, the desktop application will detect that the Web Service is unavailable. But what will happen to the requests that already made into the internal queue of my Web Service? They will be lost forever, and nobody will know about them.
There are multiple solutions for this problem including saving the unprocessed requests into a database table for later processing. What I decided to do is write simple routines that store requests in the queue to a local file when the Web Service is shutting down and load requests from the local file into the queue when the Web Service is starting up. This way, no data will be lost when the Web Service is shutdown or restarted.
PersistLib.dll
I built a C# library PersistLib.dll, which contains a Utility class. This class has the following two simple functions:
public static void StoreData(object oData, string sFilePath);
The StoreData function will serialize the oData object into a file identified by the sFilePath string. If sFilePath is null, then the file will be DataFile in a subfolder called DataFolder located in the same folder as PersistLib.dll.
public static object LoadData(string sFilePath);
The LoadData function will return a data object deserialized from a file identified by the sFilePath string. If sFilePath is null, then the file will be DataFile in a subfolder called DataFolder located in the same folder as the PersistLib.dll. If no such file exists, the return value will be null.
You can use the above two functions with all kinds of data objects, such as arrays of strings and numbers, hashtables, queues, etc. The only catch is, the objects involved have to be either a primitive type or a serializable type. Please note that the .NET class Array is serializable, therefore the above functions will work with arrays of arrays of arrays of serializable objects.
Here is a simple test program for this library:
class Test
{
[STAThread]
static void Main(string[] args)
{
object[] pData1 = {"Hello", "World",
"How are you", 234, -3.03};
Hashtable oHash1 = new Hashtable();
oHash1.Add(1, "Item1");
oHash1.Add(2, "Item2");
oHash1.Add(3, "Item3");
PersistLib.Utility.StoreData(new object[2]{pData1,oHash1},null);
object[] pData = (object[])PersistLib.Utility.LoadData(null);
object[] pData2 = (object[])pData[0];
Hashtable oHash2 = (Hashtable)pData[1];
Console.Out.WriteLine("\r\nArray Items: ");
for(int i=0;i<pData2.Length;i++) Console.Out.WriteLine(pData2[i].ToString());
Console.Out.WriteLine("\r\nHashtable Items: ");
for(int i=1;i<=3;i++) Console.Out.WriteLine(oHash2[i].ToString());
}
}
The above code demonstrates that you can easily store and load pretty complicated objects and arrays. Here is the output of this test program which shows that data is stored and loaded correctly:
Array Items:
Hello
World
How are you
234
-3.03
Hashtable Items:
Item1
Item2
Item3
Going back to my original problem, all I need to do is modify my Web Service so that it:
- calls
LoadData in the Global.Application_Start method to read data from the local data file into the internal queue, and
- calls
StoreData in the Global.Application_End method to write data in the internal queue to the local data file.
Limitations
First, if your object is non-serializable, then you cannot save data properly using this library. Secondly, suppose you made your object serializable, but later you modified the data structure (adding or deleting a field, for example), then you may have problems using the library to load the data saved before you made the change. Thirdly, there is a slight chance that installing a new version of .NET can cause the same problem (a new field is added to a .NET serializable class, for example).
Anyway, the approach described here is not meant to be a fool-proof solution, and the article is not a formal discussion about the pros and cons of using .NET serialization. Thanks.