Web Connection Server applications are in essence FoxPro applications that are loaded once and stay in memory. This means they have state that sticks around for the lifetime of the application. Persistance in time…
Global State: The wwServer object
In Web Connection the top level object that always sticks around and is in effect the global object, is the wwServer instance. Any property/object that is attached to this instance, by extension then also becomes global and is effectively around for the lifetime of the application.
What this means is that you can attach properties or resources to your wwServer instance easily and create cached instances of objects and values that are accessible via the
Server private variable anywhere in your Web Connection connection code.
This is useful for resource hungry components that take a while to spin up, or for cached resources like large look up tables or collections/arrays of values that you repeatedly need but maybe don't want to reload on each hit.
Attaching Application State to wwServer
There are a number of ways to attach custom values to the global wwServer instance:
- Add a Property to your Server Instance
Adding Properties to wwServer Explicitly
You can explicitly add properties to your wwServer instance. Your custom wwServer instance is in
MyAppMain.prg (Replace MyApp with whatever your appname is) and in it is a definition for a server instance:
DEFINE CLASS MyAppServer as wwServer OLEPUBLIC oCustomProperty = null PROTECTED FUNCTION OnInit this.oCustomProperty = CREATEOBJECT("MyCachedObjectClass") ... ENDFUNC ENDDEFINE
oCustomProperty value or object is loaded once on startup and then persists for the duration of the Web Connection server application.
You can then access this property from anywhere in a Process class as:
loCustom = Server.oCustomProperty
And voila you have a new property that exists on the server instance and is always persisted.
COM Interfaces vs new Server Properties
One problem with this approach is that the new property causes a COM Interface change to the COM server that is gets registered when Web Connection runs as a COM server. Whenever the COM interface signature changes, the COM object needs to be explicitly re-registered or else the server might not instantiate under COM.
So, as a general rule it's not a good idea to frequently add new properties to your server instance.
One way to mitigate this is to create one property that acts as a container for any persisted objects and then use that object to hang off any other objects:
DEFINE CLASS ObjectContainer as Custom oCustomObject1 = null oCustomObject2 = null oCustomObject3 = null ENDDEFINE
Then define this on your wwServer class:
DEFINE CLASS MyAppServer as wwServer OLEPUBLIC oObjectContainer = null PROTECTED FUNCTION OnInit this.oObjectContainer = CREATEOBJECT("ObjectContainer") ... ENDFUNC ENDDEFINE
You can then hang any number of sub properties off this object and still access them with:
loCustom1 = Server.oObjectContainer.oCustomObject1 loCustom.DoSomething()
The advantage of this approach is that you get to create an explicit object contract by way of a class you implement that clearly describes the structure of the objects you are ‘caching’ in this way.
For COM this introduces a single property that is exposed in the external COM Interface registered - adding additional objects to the container has no impact on the COM Interface exposed to Windows and so no COM re-registration is required.
The Web Connection Server class includes an oResources object property that provides a generic version of what I described in the previous section. Rather than a custom object you create, a pre-created object exists on the server object and you can hang off your persistable objects off that instance.
You can use:
AddProperty(propname,value)to create a dynamic runtime property
Add(key,value)to use a keyed collection value
.AddProperty() like the name suggests dynamically adds a property to the
PROTECTED FUNCTION OnInit this.oResources.AddProperty("oCustom1", CREATEOBJECT("CustomClass1")) this.oResources.AddProperty("oCustom2", CREATEOBJECT("CustomClass2")) ... ENDFUNC
You can then use these custom properties like this:
loCustom1 = Server.oResources.oCustom1
The behavior is the same as the explicit object described earlier, except that there is no explicit object that describes the custom property interface. Rather the properties are dynamically added at runtime.
.Add() works similar, but doesn't add properties - instead it simply uses collection values.
PROTECTED FUNCTION OnInit this.oResources.Add("oCustom1", CREATEOBJECT("CustomClass1")) this.oResources.Add("oCustom2", CREATEOBJECT("CustomClass2")) ... ENDFUNC
This creates collection entries that you retrieve with:
loCustom1 = Server.oResources.Item("oCustom1") loCustom2 = Server.oResources.Item("oCustom2")
This latter approach works best with truly dynamic resources that you want to add and remove conditionally. Internally
wwServer::oResources method uses a wwNameValueCollection so you can add and remove and update resources stored in the collection quite easily.
Persistance of Time
One of the advantages of Web Connection over typical ASP.NET multi-threaded COM servers applications in ASP.NET where COM servers are reloaded on every hit, is that Web Connection does have state and the application stays alive between hits. This state allows the FoxPro instance to cache data internally - so data buffers and memory as well as property state can be cached.
You can also leave cursors open and re-use them in subsequent requests. And as I've shown in this post, you can also maintain object state by caching it on the wwServer instance. This sort of ‘caching’ is simply not possible if you have COM servers getting constantly created and re-created.
All this adds to a lot of flexibility on how manage state in Web Connection applications. But you also need to be aware of your memory usage. You don't want to go overboard with cached data - FoxPro itself is very good at maintaining internal data buffers, especially if you give it lots of memory to run in.
Be selective in your ‘caching’ of data and state and resort to caching/persisting read-only or read-rarely data only. No need to put memory strain on the application by saving too much cached data. IOW, be smart in what you cache.
Regardless, between Web Connection's explicit caching and FoxPro's smart buffering and memory usage (as long as you properly constrain it) you have a lot of options on how to optimize your data intensive operations and data access.
Now get too. Time's a wastin'…