Rick Strahl's FoxPro and Web Connection Web Log
White Papers | Products | Message Board | News |

Web Connection Web Control Framework Pages and Page Code Reuse


Monday, July 9, 2007, 8:29:00 PM

A number of questions have recently come up regarding  dynamic execution of Web Control Framework Pages in the Web Connection framework. In this post I’ll review briefly how Web Control Pages work and then dig into some ways of how you can access these pages dynamically from regular Process class method code as opposed to only via direct script access and on a new feature that provides all of the functionality that direct script access provides via a simple Response.ExpandPage method.

A quick review of Web Control Framework Pages

The Web Control Framework is Web Connection’s ASP.NET like framework that provides a rich page model for creating page based applications. A Page in this context is basically a fully self contained HTML layout that is made up of static HTML text and any number (or none) of controls that provide page features and represented in Visual FoxPro as an object that contains other objects – controls and literal controls specifically.

 

Page classes can be manually generated using pure code, but more commonly visual markup is used. The visual layout uses ASP.NET style control syntax so you can edit Web Control Framework pages in Visual Studio and get full design time and property sheet support including the ability to the drag and drop controls and get Intellisense support for their functionality.

 

Markup pages look like this:

 

<%@ Page Language="C#" %>

<%@ Register Assembly="WebConnectionWebControls"

    Namespace="Westwind.WebConnection.WebControls"

    TagPrefix="ww" %>

 

<ww:wwWebPage ID="HelloWorld_Page" runat="server"

              GeneratedSourceFile="webcontrols\helloworld_Page.prg" >

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Hello World Test</title>

    <link href="westwind.css" rel="stylesheet" type="text/css" />

</head>

<body style="margin-top:0px;margin-left:0px">

    <form id="form1" runat="server">

 

    <ww:wwWebErrorDisplay runat="server" id="ErrorDisplay" />

 

    Enter your name: <ww:wwWebTextBox id="txtName" runat="server" />

    <ww:wwWebButton id="btnSubmit" runat="server" Text="Say it"

                    Click="btnSubmit_Click"  />

    <hr />

    <ww:wwWebLabel id="lblMessage" runat="server" />

    </form>

</body>

</html>

</ww:wwWebPage>

 

and they can be designed in the Visual Studio WYSIWYG designer where you can drag and drop Web Connection controls, use the property sheet to assign properties etc.

 

Web Connection then supports generating code from the markup page, translating the HTML markup and control definitions into a FoxPro PRG file that contains the following:

 

  • A small stub loader for the Page class so you can just DO MyPage.prg
  • A base class that is used to attach your user code
  • A generated class that inherits from the base class

 

 

The whole thing looks like this:

 

#INCLUDE WCONNECT.H

 

*** Small Stub Code to execute the generated page

PRIVATE __WEBPAGE

__WEBPAGE = CREATEOBJECT("HelloWorld_Page_WCSX")

__WEBPAGE.Run()

RELEASE __WEBPAGE

RETURN

 

**************************************************************

DEFINE CLASS HelloWorld_Page as WWC_WEBPAGE

***************************************

*** Your Implementation Page Class - put your code here

*** This class acts as base class to the generated page below

**************************************************************

 

FUNCTION OnLoad()

 

ENDFUNC

 

FUNCTION btnSubmit_Click()

this.lblMessage.Text = "Hello " + this.txtName.Text

ENDFUNC

 

ENDDEFINE

 

 

 

*# --- BEGIN GENERATED CODE BOUNDARY --- #*

 

*******************************************************

*** Generated by WebPageParser.prg

***    on: 07/09/2007 03:14:07 PM

***

*** Do not modify manually - class will be overwritten

*******************************************************

 

DEFINE CLASS HelloWorld_Page_WCSX AS HelloWorld_Page

 

   Id = [HelloWorld_Page]

 

   *** Control Definitions

   form1 = null

   ErrorDisplay = null

   txtName = null

   btnSubmit = null

   lblMessage = null

 

FUNCTION Initialize(loPage)

LOCAL __lcHtml

DODEFAULT(loPage)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   [<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">]+ CRLF +;

   [<html xmlns="http://www.w3.org/1999/xhtml">]+ CRLF +;

   [<head>]+ CRLF +;

   [  <title></title>]+ CRLF +;

   [  <link href="westwind.css" rel="stylesheet" type="text/css" />]+ CRLF +;

   [</head>]+ CRLF +;

   [<body style="margin-top:0px;margin-left:0px">]+ CRLF +;

   []

CTL0003 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0003")

CTL0003.Text = __lcHtml

THIS.AddControl(CTL0003)

 

 

THIS.form1 = CREATEOBJECT("wwWebform",THIS,"form1")

THIS.AddControl(THIS.form1)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   []+ CRLF +;

   []

CTL0005 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0005")

CTL0005.Text = __lcHtml

THIS.AddControl(CTL0005)

 

 

THIS.ErrorDisplay = CREATEOBJECT("wwweberrordisplay",THIS,"ErrorDisplay")

THIS.AddControl(THIS.ErrorDisplay)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   []+ CRLF +;

   [  Enter your name: ]

CTL0007 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0007")

CTL0007.Text = __lcHtml

THIS.AddControl(CTL0007)

 

 

THIS.txtName = CREATEOBJECT("wwwebtextbox",THIS,"txtName")

THIS.AddControl(THIS.txtName)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   []

CTL0009 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0009")

CTL0009.Text = __lcHtml

THIS.AddControl(CTL0009)

 

 

THIS.btnSubmit = CREATEOBJECT("wwwebbutton",THIS,"btnSubmit")

THIS.btnSubmit.Text = [Say it]

THIS.btnSubmit.HookupEvent("Click",THIS,"btnSubmit_Click")

THIS.AddControl(THIS.btnSubmit)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   [  <hr />]+ CRLF +;

   []

CTL0011 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0011")

CTL0011.Text = __lcHtml

THIS.AddControl(CTL0011)

 

 

THIS.lblMessage = CREATEOBJECT("wwweblabel",THIS,"lblMessage")

THIS.AddControl(THIS.lblMessage)

 

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   []

CTL0013 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0013")

CTL0013.Text = __lcHtml

THIS.AddControl(CTL0013)

 

 

CTL0014 = CREATEOBJECT("wwWebForm",THIS)

CTL0014.RenderType = 2

THIS.AddControl(CTL0014)

 

__lcHtml = []

__lcHtml = __lcHtml + []+ CRLF +;

   [</body>]+ CRLF +;

   [</html>]

CTL0015 = CREATEOBJECT("wwWebLiteral",THIS,"CTL0015")

CTL0015.Text = __lcHtml

THIS.AddControl(CTL0015)

 

 

ENDFUNC

ENDDEFINE

 

*# --- END GENERATED CODE BOUNDARY --- #*

 

The stub code at the top is basically a loader that knows how to instantiate the page and run it through the Page pipeline. The page pipeline fires a set of standard (like OnInit, OnLoad, OnPreRender, Dispose etc.) and user activated events (like button clicks, or selections in lists). The class relies on the various Web Connection intrinsic objects such as Request, Response, Server, Session, Config being available so it must be run in the context of a wwProcess request.

 

The first class is a base class that can be used to attach any user code to the page. This includes startup or custom rendering code, event handlers for clicks and selection changes, databinding expressions and any amount of custom methods and properties that you’d like to add to the class. Anything that is application specific can be put here.

 

The code to above the generated Code boundary ONE TIME GENERATED. Meaning if the PRG file does not exist and you run the page (when page parsing is enabled) the PRG file is created and all three components are generated. Once the page exists any changes made to the page markup cause only the bottom section of the page that contains the generated code to be updated. Any code above the boundary is never touched although the code file is updated every time a markup change is made.

 

 

Page Inheritance

Note that the generated class inherits from the base class above.Inheritance is used to allow keeping the changes to user code separate from the markup changes. By using a separate class we can isolate the code that you write from the code that is generated.

 

Because the class that you write code on is the base class you can also change the base class for your user code class to something other than the default wwWebPage class. This means you can use a custom page class that is application specific or maybe specific to your framework.

 

But even more interesting is that you can also create a base class that is shared by multiple markup pages. So you could create HelloWorld_Base as wwWebPage and then create several different HelloWorld.wscx variations that create empty classes that simply inherit from Helloworld_base.

 

You could start with this class in a separate PRG file:

 

**************************************************************

DEFINE CLASS HelloWorld_Base as WWC_WEBPAGE

***************************************

*** Your Implementation Page Class - put your code here

*** This class acts as base class to the generated page below

**************************************************************

 

FUNCTION OnLoad()

 

ENDFUNC

 

FUNCTION btnSubmit_Click()

this.lblMessage.Text = "Hello " + this.txtName.Text

ENDFUNC

 

ENDDEFINE

 

Then for the actual WCSX code behind page you’d use:

 

#INCLUDE WCONNECT.H

 

SET PROCEDURE TO Helloworld_base.prg

 

*** Small Stub Code to execute the generated page

PRIVATE __WEBPAGE

__WEBPAGE = CREATEOBJECT("HelloWorld_Page_WCSX")

__WEBPAGE.Run()

RELEASE __WEBPAGE

RETURN

 

**************************************************************

DEFINE CLASS HelloWorld_Page1 as HelloWorld_base

***********************************************

 

ENDDEFINE

 

Using this approach you could now have several different versions of the Helloworld page that inherits the same functionality:

 

**************************************************************

DEFINE CLASS HelloWorld_Page2 as HelloWorld_base

***********************************************

ENDDEFINE

 

As long as the code in the base class can count on any objects referenced to exist this sort of inheritance allows you reuse functionality easily across pages.

Running Web Control Pages

By default the Web Connection framework takes care of running Web Control Framework pages. It does so through a fairly complex mechanism in wwProcess::RouteRequest that checks the mode that the framework is in and based on that decides whether to parse and precompile WCF pages. It looks for the page on disk parses out the generated PRG file and then eventually executes this PRG that runs the page. There’s some caching and optimization that deals with deciding when to compile when to just run and so on.

 

This functionality is the default and it works in 99% of scenarios. It’s based around the concept of a physical file on disk and a matched PRG file that acts as a backing file. The default behavior always looks at the physical file first to figure out the location of the PRG file to execute.

 

There have been a number of questions of how to run a Web Control page through a custom path or reuse the same page from multiple virtual directories (common in scenarios where the same code and markup is used for different associations/groups/companies just with different data).

 

Web Control pages are classes and they are self contained. Once you’ve generated a PRG file and class from a markup page, the PRG file is fully self-contained and can execute inside of the Web Connection Process environment. The PRG doesn’t need a markup file and it contains everything it needs to ‘run’ the page effectively on its own. All the default parsing and ‘routing’ code is not required once the PRG exists and your code knows how to find it.

 

A simple DO HelloWorld_Page.prg is all that it takes to execute the page!

 

This means you can fairly easily create custom routing schemes to Web Control Pages using standard Web Connection Process class methods. For the uninitiated: the wwProcess class is the base handler that responds to a specific type Web Connection request – for example for a specific extension like .wdd. Process classes have various levels of default request routing. The first and highest priority route maps a page name (ie. Helloworld.wwd – page name is HelloWorld) to method of the same name if it exists. This is the most basic and most efficient map that is known as classic wwProcess method handling. If no method can be found script execution is used instead and Web Connection tries to find a script file on disk and execute it as a Web Page. If still no match is found the request fails.

 

So the important concept is that you can use plain wwProcess method to fire a Web Control page. Assuming Helloworld_Page.prg exists you can call this PRG file from any Process method simply with code like this:

 

*** Process Method

FUNCTION DoHelloworld

   DO Helloworld_Page.prg

ENDFUNC

 

That’s it.

 

Because this is plain process method code and it doesn’t look at the markup file at all there’s no association of the page with the underlying markup file. IOW, you can execute this page now from anywhere in response to any request you choose!

 

But understand that when you do this you need to ensure that the markup was already parsed into a PRG file. Calling the page in this fashion is not dynamic meaning it calls the PRG file and that PRG file will not be updated if there are changes to the markup unless you explicitly compile (with WebPageParser.prg) or by running the page directly through it’s physical path script mapping.

 

A new addition: wwPageResponse.ExpandPage()

To address this scenario a little better and for pure consistency with the other scripting mechanisms build into Web Connection I decided to add a new ExpandPage() method to the wwPageResponse class, which abstracts the full page parsing, compilation and execution cycle that is natively build into the wwProcess class.

 

With ExpandPage() you can now simply point at the physical path of a script page and Web Connection will then execute the page directly from a Process method. So here the code becomes:

 

*** Process Method

FUNCTION DoHelloworld

   *** Point at the original script file

   lcPath = this.WebScriptPath

   Response.ExpandPage( lcPath + "Hellowold.dp")

ENDFUNC

 

This code differs from calling the PRG directly in that it will parse and compile the page first just as if it was executed by direct URL access. There’s another optional parameter that specifies the Page Parsing mode which defaults the value set by wwProcess.nPageParseMode (1 – parse & run 2 – parse & compile & run 3 – run only) and which are also exposed in the Status Forms Page Parse Mode dropdown.

 

ExpandPage() pretty much gives you full control over the process. One note: It’s specific to wwPageResponse only and not by wwResponse/String/File since it relies on a host of features in the new Response class.

 

So with this functionality in place it should now be possible to use one set of pages and codebehind classes to service multiple applications/virtual directories with a single set of pages. This is definitely a specialty scenario but this seems to come up pretty consistently.

 

It should also make it easier yet to integrate WebControl functionality into existing applications and I’m hoping the easier it is to hook this functionality into existing applications the easier it will be for people to use this stuff!

 

ExpandPage will be released with the next update of Web Connection (5.30 or so).

Posted in:

Feedback for this Weblog Entry


Re: Web Connection Web Control Framework Pages and Page Code Reuse



RandyP
Looks like you need "ADDITIVE" added to this line in your example of using inheritance:

SET PROCEDURE TO Helloworld_base.prg

Re: Web Connection Web Control Framework Pages and Page Code Reuse


Thanks Randy - sharp eyes on you <s>...
 



© Rick Strahl, West Wind Technologies, 2003 - 2018