he Web application with which I am currently involved, relies heavily on external data abstractions to control both functionality, as well as the UI appearance. These external data repositories are all stored in XML format.
- Application Configuration
- Navigation Configuration (Modes, Modules and Menus)
- XSL Stylesheets
- String Resources
- Page Templates
The benefits in storing this type of data externally is that the functionality, as well as the look-and-feel of the application can be changed quickly by simply altering the data in the appropriate external file. Need to add a new menu item? Simply modify the Navigation configuration file. Want to change the stylistic appearance of the UI? Simply modify the relevant XSL stylesheet and/or page templates. Need to correct a typo, or change a bit of verbiage? Simply make the modification in the string resource file. No code modifications are necessary in order to effect these, and other similar changes.
You might find it interesting to stop by www.FPSNow.com in order to take a look at the application which I’ve described. The site itself contains a wealth of documentation on how it is constructed which you may find interesting, as it relates to this article.
On the other hand, loading these data files, and instantiating the XML DOM’s (that’s the kicker) on each transaction would be an unnecessary burden since the same operation would be repeated over and over again for successive transactions. By pooling the objects, and leaving the internal DOM structures loaded, we can re-use initialization on successive transactions. As it turns out, pooling objects is simple with Visual Basic.NET.
Note: the following discussion presents a software solution which has been developed and tested under Windows 2000 Server exclusively. I have no experience with .NET in any previous environments and so I can’t comment on whether the scenario I am about to present has any application to those environments at this time. (Although some aspects of what I am about to present can be useful at least in concept, if not in direct application.)
The .NET framework provides the IHttpHandler interface for objects instantiated via IIS. This interface provides the class with two important benefits.
- The class will have access to the IIS HTTPContext.
- The class can be pooled by IIS.
A class which implements IHttpHandler (I’ll call classes of this type, HTTPHandlers) is invoked by mapping a url to the class in the Web.config file which is present in the root folder of the application. Here are two lines which map two types of url’s to a Handler class.
According to these specifications, requesting the specific URL enternet.aspx, or any url ending in .htmx from this application site will cause IIS to instantiate a copy of EnterNetMain.WAC to handle the request. There doesn’t necessarily need to be any information inside of the designated url. For example, here is a listing of the file enternet.aspx, which is mapped in the first of the two statements above.
<%@ Page Language="vb" %>
' This page does absolutely nothing. It simply serves as the
' cross-reference URI mapping to invoke the Web Application
' Controller, EnterNetMain.WAC.
As you can see, there’s nothing of substance in the file. The file just acts as a trigger’ to launch EnterNetMain.WAC, which is an HTTPHandler.
HTMX files (the .htmx extension is my own definition) are another matter completely. HTMX files contain data which is processed by the HttpHandler. The instance of the class instantiated (or selected from the pool) to process each particular request knows which particular HTMX file was requested since it has access to the HTTPContext.
Ultimately, whether the file indicated by the url has any meaningful information in it or not is up to your particular implementation. The only thing you need to understand, is how to map a particular url or group of url’s to an HTTPHandler, as described above.
There is one additional step which may need to be taken when mapping a url to an HTTPHandler and that is to ensure that the url is handled by the ASP.NET ISAPI (aspnet_isapi.dll). ASPX files are by default handled in this manner so if you simply want to map an ASPX file, all you need to do is make the appropriate entry in Web.config as shown above. HTMX files are a brand new file type though, so this new file type needs to be explicitly mapped to ASP.NET ISAPI. This is easily accomplished through the IIS management console. You can set the new mapping at the server, site or application level. Here’s how to set it at the application level.
- Right-click on the application and select Properties.
- On the Directory tab, click the Configuration button.
- On the App Mappings tab click the Add button.
- In the dialog enter the fully qualified path to the ASP.NET ISAPI DLL.
(On my system it is C:WINNTMicrosoft.NETFrameworkv1.0.2914aspnet_isapi.dll)
- Enter the extension you wish to map (e.g. .htmx – don’t forget the dot’)
- Hit OK.
Simple Code Shell
Now that you’ve got a mapping for an HTTPHandler class, let’s take a closer look at a simple code shell for such a class.
Public Class WAC : Implements System.Web.IHttpHandler
Shared s_InitGUID As String
Private m_InitGUID As String
Public Sub ProcessRequest(ByVal Context As HttpContext) _
‘ Process the transaction…
Public ReadOnly Property IsReusable() As Boolean _
Public Shared Sub Refresh()
‘ This sets the shared InitGUID to a different
‘ value than that of the module level InitGUID.
‘ This will trigger an initialization on the
‘ next object access, at which time a new shared
‘ InitGUID will be assigned. Following this, all
‘ loaded instances will one by one reinitialize
‘ themselves on their next access.
s_InitGUID = “”
Private Sub zInit()
‘ This method takes care of the initialization
‘ of objects which are shared across transactions.
‘ Since this is a pooled object, sharing generalized
‘ overhead operations results in an increase in
‘ performance. However, care must be taken to ensure
‘ that transaction specific data from a previous
‘ transaction does not impact on subsequent transactions.
If s_InitGUID = “” Then
s_InitGUID = GUID.NewGUID.ToString
If s_InitGUID = m_InitGUID Then
‘ Do your initialization here.
‘ Then synchronize the private and shared markers.
m_InitGUID = s_InitGUID
It’s really quite simple. When IIS instantiates this class, or pulls it from the pool, it calls ProcessRequest, passing Context in as a parameter. From that point on you’ve got access to everything you need in order to process the request and send the response back to the browser.
The only other method which this interface provides is the IsReusable method which returns a Boolean. Simply return True and when control returns to IIS, it will release the object into a pool, rather than destroy it. The main thing to understand about pooling, is that the object will maintain its state between calls. This is a double edged sword. State which is generic, and relevant to all transactions across the board, should be maintained in order to eliminate the need to re-create it on successive transactions. State which is accumulated at the behest of a specific transaction must be either explicitly initialized at the beginning of ProcessRequest, or de-initialized at the end of ProcessRequest. Failing to do this might result in state from a previous transaction, having an adverse effect on a subsequent transaction.
In my application, the generic state which I maintain between transaction consists of several XML DOM’s created from external XML files. Consequently, the ability to cache this loaded information in a pooled object yields a significant performance improvement.
There is one drawback to this scheme however, since it is possible for the cached information in a pooled object to get out of sync with the information in the external files. To be sure, this is the exception rather than the rule since, by and large, the contents of these files don’t normally change on a day-to-day basis. However, it would be very convenient to have a mechanism whereby these pooled objects could be synchronized with the current external data without having to restart the web server.
I briefly considered using file modification dates in order to accomplish this, but quickly rejected this since I didn’t want to have to expend effort on each transaction for an occurrence which might happen only once a month. The method I chose leverages the new ability to define shared data variables in Visual Basic.NET.
The concept behind a shared variable is very similar to a variable in a VB6 global BAS module. That is, that all instances of the class loaded on the same thread will share that variable. Similarly, with Visual Basic.NET, all instances of a class in the same AppDomain (roughly equivalent to a process) will share the same Shared variable. So this capability provides us with the basic approach. Using a shared variable as a toggle switch, it is possible to reach out and touch’ all class instances in order to inform them that some event has or should occur.
However, a single shared data point is insufficient for the operation we are contemplating. If the shared data point was the only value involved, then the first class instance to re-initialize itself would reset the toggle to indicate its own re-initialization has occurred, thus short-circuiting the mechanism for other class instances. The solution is to utilize two data points, a shared variable which is shared across all class instances, and a private variable which is specific to each class instance. In the example above, these two data points are s_InitGUID and m_InitGUID respectively.
The way the scheme works is that, basically, anytime a class initializes it copies the shared initialization GUID to it’s own private initialization GUID. As long as those two items are in sync, then the class does not re-initialize itself. This check is a simple, low-cost, string comparison which takes place at the beginning of zInit, which is the initialization routine called upon each entry into ProcessRequest.. Although zInit is invoked on every request, it rarely completes, since usually it finds the two variables are equivalent and exits immediately.
If you’re after the ultimate tweak, and don’t mind cluttering up your main line code a bit, then you can use the following branch to avoid going into zInit in the first place, unless you actually need to.
If m_InitGUID = “” Or m_InitGUID <> s_InitGUID Then
In this case you won’t need the following conditional logic inside zInit.
If s_InitGUID = m_InitGUID Then
Here’s how the scheme works, starting from application startup:
The first class to be instantiated finds s_InitGUID = ”. It assigns a baseline GUID to s_InitGUID which is then copied to its own m_InitGUID.
Subsequent classes which are instantiated find their internal s_InitGUID = ” which is different, after all, than the shared GUID. They therefore initialize themselves and assigned the shared initialization GUID to their own internal initialization GUID.
As classes are retrieved from the pool, they find that their internal GUID matches the shared GUID. They bypass initialization.
If the shared Refresh method is called, it will reset the shared GUID string to ”.
The next class to be instantiated or retrieved from the pool, finds s_InitGUID = ”. It assigns a new baseline GUID to s_InitGUID which is then copied to its own m_InitGUID.
Subsequent classes which are either instantiated, or retrieved form the pool, find their internal GUID’s to be different, than the shared GUID. They therefore initialize themselves and assigned the shared initialization GUID to their own internal initialization GUID.
Subsequent classes which are retrieved from the pool, and have already been re-initialized, find their internal GUID’s to be equal to the shared GUID. They therefore bypass initialization.
The following table summarizes this activity.
Class needs to initialize since the GUID which marks the most recent initialization is out of date with the current shared initialization GUID.
The initialized data in this class instance is up to date.
I’d like to emphasize that, in many ways, the methodology of what I have presented is more significant than the actual details are. I’d like to focus in on two specific points.
The first point is that multiple instances of a pooled class can maintain their own copies of application wide data. Some readers may object. So what? This functionality is already available in several different manners. Even now, without pooled objects we keep our application wide data in centralized repository such as either the Application object, the Shared Property Manager (SPM) or the Global Information Table (GIT)’. These readers might also note that by using Shared class variables rather than Private class variables to maintain the data, only a single copy of the data needs to be maintained.
It is true that these are all viable alternatives. However the use of pooled objects and class private variables address certain circumstances for which the alternatives listed above are not viable. I’ll present a few examples in order to demonstrate that this method is simply one more tool in our development arsenal, to be used appropriately (as all tools should be used).
- Storing a VB6 object, or any thread-affinitive object, for global access, has always been problematic, since this, in effect, serializes every transaction which accesses the global object through a single thread. The technique presented above addresses this issue handily since each pooled transaction object maintains it’s own instance of the global access class(es) in its own class private variable(s).
- Multiple clients accessing a common data repository involves problems of synchronization if one client tries to access the data while another client is updating the data. The technique presented above eliminates this issue entirely since each client maintains its own private copy of the data.
- The use of class private data is also viable for data which is likely to be application wide, but for which exceptions are bound to occur for specific transactions. A good example of this is a resource file. If we assume that 90% of the users are native language speakers, while 10% are foreigners, then we can use an optimistic’ algorithm to retain a resource file in preparation for the next transaction. If the next transaction uses the same language (which should occur about 80% of the time) then the language file need not be loaded. If the language of the subsequent transaction differs, then the new language file is loaded. Here we have a situation where we’d like to retain cached data across transactions, yet it’s obvious that this cached data can not be shared among all instances since different simultaneous transactions might be using different language files.
‘Arm and Fire’ Methodology
These are simply a few examples of where the technique of using class private variables to cache data between transactions for pooled objects, can be useful. No doubt this technique can be useful in other situations. I’m sure as well, that other solutions could be proposed for these situations. Again, this technique is simply an additional method which is available to developers. It should be either used or avoided, as appropriate.
The second point is that I’ve used an ‘arm and fire’ methodology in order to have my objects update themselves. The Refresh method is the arming event, however the firing of the actual data refresh does not occur until the class is called upon to perform the next transaction. This is significant since it prevents all pooled objects from an unnecessary simultaneous refresh, which could destroy performance for a transaction which was actually in progress while all this mass data refresh was occurring. Indeed, on a system where the arming event occurs frequently, a given pooled instance might actually avoid participating in one or more refresh operations since it will not refresh until it is actually called upon to execute a transaction.
It is true that many different mechanisms could be used to trigger the arming event. One such mechanism which comes to mind is the use of the FileSystemWatcher object. In my scenario, were I to adopt this method, I would still use a separate trigger file. Since there are multiple base data files, I would not want the trigger to be based on a data file since that could have the effect of triggering the arming event before all modified data file were in place. The advantage to the use of the FileSystemWatcher is that the arming event could be initiated by any client, even one not running in the same AppDomain as the pooled classes are. The disadvantage to the FileSystemWatcher is that, from my perspective, this builds up the complexity of the object which is something I generally avoid where I can. Admittedly, in the absence of benchmarks this last observation is somewhat subjective, but hey, it’s my code. Feel free to use the FileSystemWatcher, or any other mechanism you’re comfortable with in your own software. In the final analysis, the specific mechanism for the arming event does not change the fundamental behavior or detract from the significance of the arm and fire methodology, which is available to us, for use in the appropriate scenarios.
A few final points:
I use a GUID for initialization markers since a GUID is practically guaranteed to be unique. This means we don’t need to explicitly choose a new initialization marker and we don’t need to worry about reusing a marker which is still in use by a loaded class. (Although technically speaking, this occurrence is possible, it is improbable to the point where we can safely deem it impossible. I’d be more worried that perhaps both of my replicated database servers, or that two disks in my RAID pack, might fail simultaneously. The use of a GUID is at least as robust, and probably far more robust, than either of these fail-safe’ mechanisms. Extremely cautious developers may find comfort in the draft specification for UUID’s and GUID’s at http://www.webdav.org/specs/draft-leach-uuids-guids-01.txt.
In the Refresh method I set the shared GUID to , rather than immediately issuing a new GUID. I can argue both sides of the issue, but I chose this approach since it leverages the algorithm which is already in place to deal with the first object instantiation. The Refresh method doesn’t need to be shared, but sharing it provides syntactic convenience since it can be called without having to explicitly create an instance of the class (as you can see in the script snippet below).
In order to access the shared members for the loaded classes, the code which calls refresh must be running in the same AppDomain (i.e. process). For an IIS application, that means that the code must be running in an ASPX script loaded from the application web site. I’m using the following script on the web site I’m currently working on. Naturally I’ll be dressing this up, fronting it with a management HTML page, but I just got this working recently and this script does the job for me for now.
<%@ Page Language="vb" %>
(Remember, depending on the isolation level of your application, running this script might affect classes in other applications as well, if they are running in the same process.)
The methodology presented here is not specific to Visual Basic.NET, or even to .NET in general. This technique can be used for any environment which supports object pooling and for any language which supports shared variables among multiple class instances. With .NET’s new object oriented features and pooling capabilities, new vistas are opened, and new possibilities are available to Visual Basic developers. I hope that this tip will contribute in some small way to help you make the most of this new and exciting environment.
If you’d like to download this article in Acrobat PDF format, please stop by my Web site at http://www.FPSNow.com and follow the links to my Articles page or to my FTP server to download Pooled Objects.PDF.