Hi Dr. Bob,
After reviewing your suggestions, I'll need to enter into a little more detail here, going through the creation/life/destruction of an IWForm instance.
Let me start with a bit of context: In older IW versions, there was a session-wide list of callbacks, owned and controlled by the IWApplication instance. This has proven to be unreliable. Mostly because unregistration of async callback methods didn't occur in a fail-proof manner, because:
a- In some cases, 3rd party controls just ingnored this requirement and never unregistered their callbacks when the control was destroyed. If for some reason the callback was called at a later time, an AV would definitely happen, because the control was gone, but the callback was still there registered. Please note that this is an unrecoverable condition, once the callback could never be unregistered and the solution was to restart a new session, which is pretty bad.
b- In other cases, any exception during the form destructor (who never experienced a DB connection failure when trying to make sure that all your data has been saved?) would mean that the controls wouldn't have oportunity to unregister their events and we go back to scenario (a) above.
Since IW 15.1 IIRC, we changed that. The PageContext instance - that was temporary - became permanent and it is an integral part of any IWForm now. The form creates the PageContext before anything else and destroys it after everything else. The PageContext (meaning the IWForm instance) owns its own callback list. The IWApplication finds the active form, dispatches to it the callback to be executed and the IWForm finds one of its child controls that should respond to it. When the form is gone, the page context and the callback list are both gone too. If the form destruction fails, doesn't matter, the callback list is either destroyed or in a limbo state and will never be used again. It won't affect the session anymore. This solves both problems (a) anb (b) above.
Now, let's see some parts of IW code:
This is the TIWForm constructor:
Code:
constructor TIWForm.CreateNew(AOwner: TComponent; const ADummy: Integer = 0);
begin
// make sure TIWApplication is always the form owner
if not (aOwner is TIWApplication) then begin
aOwner := GetWebApplication;
end;
FPageContext := CreatePageContext;
inherited CreateNew(AOwner, ADummy);
end;
The form's PageContext instance is created here and is kept alive for the whole life-time of the form.
And this is the form Destructor:
Code:
destructor TIWForm.Destroy;
begin
if Assigned(FPageContext) then begin
TIWPageContextAccess(FPageContext).FDestroying := True;
end;
inherited;
FreeAndNil(FPageContext); // Callback unregistration depends on PageContext
end;
As you can see, the PageContext object is left to be destroyed last, after the inherited destructor has been called. This guarantees that all owned controls will be destroyed while the PageContext object is still alive. The comment you see in the code above is original part of the code.
Please notice that the PageContext Destroying flag is set before calling the inherited destructor, so all the controls can know, inspecting their own FPageContext reference, that their owner form (and PageContext) is being destroyed.
There is no other place where page contexts objects are created or destroyed, not even when using templates (the template processor uses the owner form PageContext instance to render anything). There are temp component contexts which are a different class of objects but alll component contexts also point to the same IWForm's PageContext which is unique.
Now, back to the TIWCustomControl destruction/callback unregistration:
Code:
rocedure TIWCustomControl.Dispose(ADispose: Boolean);
begin
...
if ADispose then begin
if not IsDesignMode then begin
UnRegisterAsyncEvents;
end;
This is part of the TIWCustomControl.Dispose method, the same part that you mentioned in your post.
Now, this is a piece of the UnregisterAsyncEvents method of TIWCustomControl:
Code:
procedure TIWCustomControl.UnRegisterAsyncEvents;
...
begin
// PageContext owns callback registration. No need to unregister if
// PageContext is about to be destroyed
if not Assigned(FPageContext) or FPageContext.Destroying then begin
Exit;
end;
...
As you can see, each control checks for the PageContext destruction before unregistering the async callbacks.
So, according to the code above, under normal circumstances, there is no way for a control *owned by a TIWForm* to reference a non-existing PageContext.
The other possibility would be controls not owned by a TIWForm (not even indirectly). Please notice that even controls owned by a TIWFrame instance that are ultimately parented to an TIWForm are covered by the premisse that they will be destroyed before the PageContext instance that they used to register the callbacks (and that is referenced by their FPageContext field).
This leaves certain bizarre scenarios like: The user creates an IWFrame at runtime, sets an IWForm as a parent of the IWFrame and later makes another IWForm instance it's parent or, worse, makes its parent nil. To be honest I'm not even sure that something like that works (i.e. has the desired effect) in a simple VCL desktop application, let alone an IntraWeb application.
These kinds of scenarios I'd just say that are simply not supported. IMO, there is no reason to do some crazy stuff like that with an IWFrame (like trying to reuse an existing IWFrame with different IWForm instances).
In short, the only way to have an AV there is if the IWControl outlives the IWForm that it once has been rendered on. If some IW users have a very unusual test case that does that I would first try to convince they not to do this. Or they can convice me that it is an acceptable use case and we revisit this topic again
I know that some IntraWeb users - especially the most experienced ones - do things that I can't even imagine

If you have another usage scenario that breaks this logic I'd honstely like to hear.