Saturday, October 04, 2008, 4:29:00 AM
Just ran into a nice inconsistency in VFP 9.0. I’ve been adding some functionality into Web Connection to allow easy configuration for unattended COM mode. VFP 8 and later has SYS(2335,0) which basically forces a COM server to throw exceptions on any blocking user interface operation. So if you bring up a MessageBox for example, rather than hanging the COM server, you get an error thrown. This behavior works both in DLL and EXE servers and can be quite handy.
Typically SYS(2335) is only used in COM applications, but it's not all that unusual to have SYS(2335) in code that might have to cooperate with non COM code like an application startup routine or a business object factory.
Supposedly SYS(2335,0) does not have any effect on a standalone non-COM application. However, I’ve run into an edge case where it does seem to affect standalong applications. Oddly the problem doesn’t show up in the IDE runtime of VFP, but only if I run the application as an EXE.
My scenario is in Web Connection where I’ve just added an lUnattendedComMode flag to the server object. When running in File mode, Web Connection throws up a Visual FoxPro desktop form with a timer that checks for request files. The form is kept alive with READ EVENTS handler. This has been working forever.
Now, if I set my lUnattendedComMode flag Web Connection is thrown into unattended mode. In file based mode in the IDE everything still works fine and that’s how I’ve been testing. However, today I actually built an exe and ran it. The server started up and – uncerimoniuosly disappeared immediately when run from Explorer. If I now go into the VFP IDE and run that same EXE the server runs and stays up and running.
In short the EXE starts and immediately exits. Lots of head scratching ensued...
Baffled I started looking at settings and sure enough it turns out it’s the unattended mode flag. When NOT running in COM mode and running a standalone EXE READ EVENTS doesn’t hold up.
The fix for Web Connection is easy enough. Internally an additional check is added for making sure the server is actually in COM mode:
IF THIS.lUnattendedComMode AND this.lComObject
SYS(2335,0)
ENDIF
In your own applications you can check _VFP.StartMode:
IF INLIST(_Vfp.STARTMODE,2,3,5)
*** Global Reference to goWCServer
THIS.lComObject = .T.
ENDIF
By the way, be aware that the _VFP and Application objects are COM objects that REQUIRE that the VFP runtimes have been registered on a machine. If you just copy runtime files to a machine or server and don’t register _VFP or Application will fail which can cause some really tricky failures in code.
Anyway – the moral of the story is: When using SYS(2335,0) you should always make sure you explicitly check for the operational mode before setting this flag or else you may have some unexpected side effects even though the documentation states that this function has no effect on non-COM applications.
Sunday, September 21, 2008, 6:06:00 PM
A question that comes up frequently on the forums is how to access and set the header in a Web Connection Web Control page. For example, how can you set the title dynamically from code in a simple layout like this:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server" id="Header">
<title></title>
<link href="westwind.css" rel="stylesheet" type="text/css" />
</head>
The problem is that in the WCF the header is not an object so you can’t you can’t directly access the title as a property in any way. One really easy way to add dynamic functionality to the header is to add an eval expression to the header:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server" id="Header">
<title><%= this.Page.PageTitle %></title>
<link href="westwind.css" rel="stylesheet" type="text/css" />
</head>
To make this work you’ll need to add a property to the page first. Once you’ve added the property you can set in code and assign whatever value suits you:
*****************************************************************
DEFINE CLASS AjaxMethodCallback_Page as WWC_WEBPAGE OF WWC_WEBPAGE_FILE
*****************************************************************
*** Your Implementation Page Class - put your code here
*** This class acts as base class to the generated page below
*****************************************************************
#IF .F.
*** This line provides Intellisense: Ensure your path includes this page's location
LOCAL this as AjaxMethodCallback_Page_WCSX of ajaxmethodcallback_page.prg
#ENDIF
PageTitle = "Hello World"
*****************************************************************
* OnLoad
****************************************
FUNCTION OnLoad()
this.PageTitle = "Hello" + Process.User.Username + ". " + TIME()
ENDFUNC
* Onload
The property is created to have somewhere to store the value that can then be assigned. When the page is rendered the eval expression picks up the title and renders it.
You can use a similar approach for anything else you want to embed. If you want to embed script at the top or bottom you might create a placeholder string that holds that literal content.
Header Manipulation – adding content to the header
In the next release of Web Connection there will be a new wwWebHead control that maps the header object to a control. This provides a little more control over header output by giving you the ability to write out header content directly through code. You can write content at the top or bottom of the header using literal strings or controls.
For example, if you wanted inject a script tag into the page in the header you could use:
this.Page.Header.AddControlAtTop([<script src="scripts/jquery.js" type="text/javascript"></script>])
AddControlAtTop injects at the very top of the Header. AddControl() injects at the bottom – this allows a little bit of control over placement that often is necessary especially when injecting things like script tags or css styles that have to respect a certain order.
Script Manipulation from Code
The main reason this change was made now is precisely because of the additional control it affords for script placement. The next major update to Web Connection will integrate more tightly with jQuery and also use a custom utility library. This update will replace the existing Ajax controls and the wwScriptLibrary with a jQuery based equivalent version.
Since jQuery is often used in combination with other plug-ins and libraries placement of the jQuery library is crucial – specifically it should be loaded before any plug-ins or utilities, so that if any manually added plug-ins run after jQuery is loaded. The new header options allow for this precise placement necessary so that the auto-loaded (optional) jQuery script can co-exist properly with any plug-ins that might also be used in combination.
Please note that as before – it’s completely possible to turn off the auto loading of any script libraries on each of the controls that use them. The default auto loads, but you can clear out the the script location to for any resource or explicitly point it at your own locations instead of using the defaults. The goal isn’t to limit you and force you to use a packaged version, but to make using components as easy as possible without requiring any additional setup.
The header manipulation functionality can be quite useful when you need and especially in control development scenarios where you want to get everything neatly wrapped up into a single self-contained package that doesn’t require configuration of other page components.
Wednesday, September 17, 2008, 5:15:00 AM
Web Connection 5.0 introduced quite a bit of AJAX functionality in the form of a few high level controls that can be dropped onto a page. The control I use most frequently is the wwMethodCallback control which makes extremely short work of calling back to the server and activating a method in the current form. The steps for this very easy. Let's quickly review how JSON style callbacks work.
Start by dropping a wwWebMethodCallback Control onto the page:
<ww:wwWebMethodCallback ID="Proxy" runat="server">
</ww:wwWebMethodCallback>
To use this mechanism you can effectively call a server method from the client by implementing a method on the current page (or alternately as a method on any object you specify with Page being the default).
The method on the page object is just a plain function that takes an input parameter and returns a value. Parameters are passed from the client, serialized into JSON, sent to the server for processing and then result from the method call is serialized into JSON and passed back to the client. A server method is just a simple method like this:
************************************************************************
* GetEntryTotals
****************************************
*** Function: Callback method that recalcs hourly totals and rates
************************************************************************
FUNCTION GetEntryTotals(lnCustPk,lcDateIn,lcTimeIn,lcDateOut,lcTimeIn)
ltTimeIn = CTOT(lcDateIn + " " + lcTimeIn)
ltTimeOut = CTOT(lcDateOut + " " + lcTimeout)
IF EMPTY(ltTimeIn) OR EMPTY(ltTimeout)
RETURN NULL
ENDIF
loCustomer = CREATEOBJECT("busCustomer")
loCustomer.Load(lnCustPk)
loTotals = CREATEOBJECT("EMPTY")
ADDPROPERTY(TotalHours, ltTimeOut - ltTimeIn / 3600)
ADDPROPERTY(TotalAmount, loCustomer.oData.BillRate * loTotals.TotalHours)
RETURN loTotals
ENDFUNC
* GetEntryTotals
You can then call this code from the client like with a simple function:
function updateEntryTotals() {
Proxy.callMethod("UpdateEntryTotals",
[custPk,
$w("txtDateIn").value,
$w("txtTimeIn").value,
$w("txtDateOut").value,
$w("txtTimeOut").value
],
function(result) {
$w("txtTotalHours").value = result.totalhours.formatNumber("n2");
$w("txtTotalAmount").value = result.totalamount.formatNumber("n2");
}, onPageError);
}
Basically this code pulls values out of some fields on the page to send to the server, and the server processes those input values and returns an object with two properties back to the client. The client receives a callback function with the result object as a parameter which it then uses to update page content. It’s a very simple mechanism that makes it very, very easy to quickly create AJAX callbacks to the server.
BTW, the $w() calls are merely shortcuts to document.getElementById() which is function that's part of the wwScriptLibrary.js which is automatically loaded when a wwWebCallbackHandler control is on the page so functionality from the library is always available.
The example above demonstrates how to return DATA to the client – you return a value as an object and then use this ‘data’ to apply directly against individual elements on the page.
Returning Partial Rendered Page Content
AJAX callbacks are very powerful and in addition to returning ‘data’ to the client it’s also possible to return HTML. Even better with a very simple little trick you can actually return just about any part of the page from your callback.
So instead of passing updated data back to say update or add a grid column on the client – which would involve HTML code and duplication – you could render the grid on the server and send only the HTML for that grid back to the client.
This sort of thing is actually very easy because all Web Connection controls include a Render() method that generates the HTML output for that control. It’s quite possible to call .Render() on any control on the page when in the middle of a Page callback. And it doesn’t have to be single controls either – you can render a Panel for example, and all child controls of that panel will also be rendered and returned as HTML.
The following is kind of a contrived example, but it’s simple and demonstrates the point effectively. Imagine that you have a DataGrid and an ErrorDisplay control on page and you want to update both controls as part of a callback. Here’s the HTML markup:
<ww:wwWebPanel runat="server" id="panUpdatableContent" OverrideNamingContainer="True">
<ww:wwWebErrorDisplay ID="ErrorDisplay" runat="server" />
<ww:wwWebDataGrid ID="gdEntries" runat="server"
AutoGenerateColumns="false"
PageSize="7" AllowPaging="true"
cssClass="blackborder"
Width="800px"
CellPadding="4"
DataKeyNames="Pk"
>
<Columns>
<ww:wwWebDataGridColumn Expression="TTOD(TimeIn)" HeaderText="Date"></ww:wwWebDataGridColumn>
<ww:wwWebDataGridColumn Expression="'<b>' + Title + '</b><br/>' + TextAbstract(Descript,120)" HeaderText="Description"></ww:wwWebDataGridColumn>
<ww:wwWebDataGridColumn Expression="TotalHours" Format="9,999.99" Style="text-align:left"></ww:wwWebDataGridColumn>
</Columns>
</ww:wwWebDataGrid>
</ww:wwWebPanel>
<ww:wwWebTextBox runat="server" ID="txtCount" Text="3" />
<input type="button" id="btnUpdateGrid" value="Update Grid" onclick="updateGrid();" />
Notice the panel that wraps the ErrorDisplay and DataGrid controls which allows capturing the output from both of them by rendering the panel on the server. This is only necessary if you want to render multiple controls in a group which if you want to update them partially are likely to be already contained in some kind of control container. Note that I also apply OverrideNamingContainer="True" which ensures that the panel doesn’t add naming container unique names to the child controls to make it easier to reference the controls in script code.
The client code that calls this function looks like this:
function updateGrid() {
Proxy.callMethod("UpdateGrid", [$w("txtCount").value],
function(result) {
var el = $ww("panUpdatableContent").setHtml(result,true);
}, onPageError);
}
It basically picks up the the value of the txtCount control and sends it to the server in the method callback. It then receives the result (which will be the rendered content of the panel) and merges it back into the document.
The key to all of this is the server side code and how it renders the panel in the callback:
************************************************************************
* UpdateGrid
****************************************
*** Function: Callback function that updates the grid
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION UpdateGrid(lcCount)
loEntry = NewObject("ttEntry","timeTrakkerSwFox\ttEntry.prg")
IF loEntry.GetRecentEntriesForUser(VAL(lcCount),1,.f.,"TEntries") < 0
this.ErrorDisplay.ShowError("Error: " + loEntry.cErrormsg)
ENDIF
this.gdEntries.DataSource = "TEntries"
this.gdEntries.DataBind()
this.ErrorDisplay.ShowMessage("Grid Updated to " + lcCount + " items.")
lcOutput = this.panUpdatableContent.Render()
RETURN lcOutput
ENDFUNC
I just pass a parameter of the number of rows to display (a TOP query) so the number of rows returned varies based on the input I provide. This is the contrived part <s> - it’s a silly example, but it does demonstrate nicely that the grid view changes as new data is loaded. The code basically re-runs a query to retrieve only the requested data, rebinds the grid and writes a message into the ErrorDisplay control.
Then this code:
lcOutput = this.panUpdatableContent.Render()
is all it takes to re-render the panel including the datagrid and the error display.
Additionally – with one small change – you can also keep request state intact for things like the active page and sort order and so on that was rendered on the server. Because by default callback requests run through the page cycle up to the Load event, things like sorting and page number state has already been picked up by the page so when the grid is re-rendered that information is also reset.
For this to happen though one small change is required:
<ww:wwWebMethodCallback ID="Proxy" runat='server'
ScriptLocation="~/scripts/wwscriptlibrary.js"
PostBackMode="Post" >
</ww:wwWebMethodCallback>
By default the PostBackMode only posts back the parameters you provide explictly in method calls. However, if you use Post or PostNoViewState the form data of all the current fields on the page are also posted to the server. When this happens the page effectively runs through a full load cycle meaning that POST data is loaded into fields nad things like page numbers and sort orders on the grid for example (which are stored in ViewState) are also reset.
Partial rendering is a powerful tool to allow you to use server rendered output on the client for AJAX updates without having to recreate HTML from scratch in JavaScript code.
Some Caveats of Partial rendering
Partial rendering can work great in many scenarios especially if you have complex layouts that are created server side. But it’s important to keep a few limitations of this approach in mind.
Basically partial rendering works only if the controls in question are not updated explicitly on the client. For example, imagine that I have a ListBox on the page and I add one or more values to it on the client, then requiest the server to do partial rendering of a panel that includes the listbox – those values added on the client aren’t going to be included because the server has no idea that the values in the list have changed unless you explicitly post them to the server.
The basic rule to partial rendering is that anything changed on partially updated content should only be changed on the server. This means either full post backs or using AJAX callbacks that end up re-rendering the control. In the list box example this means if you add an item to the list box, you’d have to send the server a message that the list has changed by sending the updated item and maybe adding it to the database, so the next time the list is rendered on the server the new value is available.
It depends entirely on your application whether this limitation is an issue or not.
Another issue to watch out for is the size of the partial content. If your content is too large partial rendering can be slow which is both annoying to the user and taxes your network bandwidth. It’s tempting to do updates of a large chunk of a page, but it’s often much more efficient to updates in smaller atomic units that update quickly.
With these caveats in mind partial rendering can be a huge time saver because it essentially allows you to create rendering content once and then reuse it for AJAX updates. I’ve used this approach in a number of applications with great results.
Check out partial rendering if you haven’t before – Web Connection makes this process very easy and it’s one of the quickest ways to get productive with AJAX.
Saturday, August 16, 2008, 4:26:00 PM
I just got done upgrading my live Web Server to a new high powered 64 Bit server. The old machine was getting a little bit long in the tooth with its classic Pentium 4 and meager 1 gig configuration. It's actually surprising just how well this old box handled the variety of traffic and volume handled by this site.
I decided to get a fairly powerful new machine: A quad Xeon (2.5ghz), with 8 gigs and 2 high speed 10k boot and drives plus a separate backup drive. Compared to the old box this machine is an incredible screamer and quite a few folks have commented of how performance has improved noticeably since the upgrade. It's not just on the outside end but also on the backside end that this is a huge improvement - in the past the biggest bottleneck on the machine was a few Web Connection queries that are very slow and were often overwhelming the single CPU. The new quad CPU's give these requests considerably more breathing room as they don't tie up the only processor and also because the queries themselves run significantly faster due to the new drives and faster clock-speed.
32 Bit Mode for Application Pools in IIS 7 - Yeah!
Anyhow, in the process of getting the new machine I also switched to Windows Server 2008 64 bit. I was a little wary about making this switch primarily because I was worried about my Web Connection applications that are running a 32 bit ISAPI DLL and 32 bit Visual FoxPro. Most of the other apps on the site are ASP.NET and can and do run now natively in 64 bit.
In IIS 5/6 that would have been a fatal issue given that IIS 6 and 5 required that the Web server is switched into 32 bit mode entirely in order to run ANY 32 bit applications, like my ISAPI assembly. It turns out IIS 7 makes this process much more granular with the ability to switch an IIS Application Pool into 32 Bit mode rather than the entire Web Server (IIS5/6):

That 'little detail' right there addressed my biggest concern about the Web Connection apps running on the 64 bit version of Windows Server.
I hooked up this Application Pool in the original test builds of the OS I had created and ran a two day load test on the existing Web Connection sites and the apps chugged away at outrageous speed on this hardware mind you and without any hiccups. Looks like the 32 bit apps (Web Connection ISAPI + Visual FoxPro COM) will have no issues whatsoever on the 64 bit server (These informal tests BTW, managed nearly 200 Web Connection requests a second!).
The new quad processor is going to help some of the Web Connection Apps tremendously too. There are a few rather slow requests that have been chugging up the CPU to near 100% levels at times for a handful of particularly long queries, but with the quad these queries will no longer hold the entire machine at ransom. Testing those particular scenarios resulted in smooth operations which solves yet another issue that has been the cause of occasional server lockups in the past.
FWIW, the same approach can also be used for other VFP applications running inside of IIS. For example if you VFP COM objects running in ASP or ASP.NET those applications also have to switch to 32 bit mode. Most applications that use ISAPI or ISAPI filters and haven't been specifically re-written to support 64 bit too need to run 32bit as well.
A few small Hiccups
This process is indeed working out great with one exception: I had a few Web Connection requests running on the root site. There are about 5 pages that run on the root and because I've set up the root site without 32 bit compatibility these request are now failing.
I can't change the root site to 32 bit tough because this would affect several other applications that are now running in integrated mode at the 'root' level. So switching to 32 bit mode is not an option.
My solution to this problem was to create map the Web Connection Script Maps to ASP classic and then redirect the requests to a location inside of a Web Connection virtual. So I effectively moved the script files from /westwindnews.wst to /wconnect/westwindnews.wst and have the original URLs redirected to this new location.
To do this there's the ASP classic mapping:

I then create an ASP page that does the following:
<%
Response.Redirect("/wconnect/westwindnews.wst")%>
The redirected URL is in another virtual that does have the 32 bit flag set and can also process this extension.
I realize this is pretty ugly but it works to effectively let me run all of my Web Conection code in one place.
So why not use the Managed Module?
Starting with Web Connection 5.0 there's a Web Connection Managed Module that can run natively in 64 Bit (or 32 bit) in IIS 7 using integrated mode. This .NET module is written from the ground up in .NET code and provides functionally the same behavior (plus a number of additional features) as the ISAPI DLL.
Unfortunately due to the way the apps are set up on my server I am not able to run the new module. The issue is that I have one COM server that services 8 different virtual directories/applications. One limitation the .NET module is that each virtual directory is effectively its own AppDomain (.NET 'internal' process) and so each virtual is independent and can't interact with another's internal structures. The module manages the Web Connection Server COM object pool but each would get its own COM pool instance and so end up creating their own instances.
A workaround for this would be to consolidate all applications under a single virtual (not often possible), use file based messaging (which works fine across virtuals) or allow apps to run their own server instances.
Neither of these options would have been effective in my case and would have required quite a bit of reworking. So, instead I stuck with the easy solution of continuing to use the ISAPI extension which can run across multiple virtuals and so manage multiple applications with a single COM pool and that's working out just fine now.
No fear of IIS 7 and 64 bit
So the good news is that on Windows Server 2008 at least, running Web Connection apps in 64 bit is no problem. With the 32 bit flag for the Application Pool it's possible to get your existing apps up and running with practically no changes at all, which is good news given that much of the new server hardware that's coming out as well as the server software is more and more geared towards 64 bit computing.
It's good to know that Web Connection and VFP is ready and that it works well and reliably. The new server's been now up for one week and the crash and restart numbers that were somewhat frequent on the old server have diminished to almost zero - the extra horse power and memory are certainly helping to make the apps more stable.
Thursday, July 31, 2008, 4:47:00 AM
A question came up today on how to handle file uploads in the context of a Web Control Framework page in Web Connection. Http File uploads are a common HTTP construct and Web Connection has long supported this process in its classic mode of operation.
Classic Http File Uploads
In classic Web Connection applications file uploads are pretty much handled through the raw mechanics of the Http protocol which involves basically two steps:
Uploads are typically handled through a separate form (or a single form) that posts back to the server via a special enctype of multipart/form data, which is required in order to force the content to be uploaded to the server.
Here's what the HTML of this process looks like:
<form enctype="multipart/form-data" method="post" action="/wconnect/FileUpload.wwd">
<p>
<input type="file" size="20" name="File"/><br/>
<br/>
File Description:<br/>
<textarea cols="43" name="txtFileNotes" rows="4"/><br/>
<input type="submit" name="btnSubmit" value="Upload File"/>
</p>
</form>
To pick up the file on the server, you can simply use the standard wwRequest object which automatically detects the content type of the posted data and returns Form data appropriately for this content. To retrieve the file and its name a little more work is required by calling the special Request.GetMultipartFile() to retrieve the file. All other form vars can be retrieved using the standard Request.Form() method.
Here's is the Fox code to pick up the file in a classic Process method:
FUNCTION FileUpload
************************************************************************
* wwDemo :: FileUpload
*********************************
*** Function: Demonstrates how to upload files from HTML forms
*** This function requires that multipart forms are
*** used on the client (multipart/data)
***************************************************************************
lcFileName = ""
*** This works too but doesn't retrieve the file name
* lcFileBuffer = Request.Form("File")
*** Files must use GetMultipartFile to retreive the file name as well
lcFileBuffer = Request.GetMultiPartFile("File",@lcFileName)
lcNotes = Request.Form("txtFileNotes")
lcFileName = SYS(2023)+"\"+lcFileName
IF LEN(lcFileBuffer) = 0
THIS.StandardPage("Upload Error","An error occurred during the upload or the file was too big.")
RETURN
ENDIF
IF LEN(lcFileBuffer) > 500000
THIS.StandardPage("File upload refused",;
"Files over 500k are not allowed for this sample...<BR>"+;
"File: " + lcFilename)
RETURN
ENDIF
*** Now dump the file to disk - for whatever purpose
File2Var(lcFileName,lcFileBuffer)
THIS.StandardPage("Thank you for your file upload",;
"<b>File Uploaded:</b> " + lcFileName + ;
" (" + TRANSFORM(FileSize(lcFileName),"9,999,999") + " bytes)<p>"+ ;
"<b>Notes:</b><br>"+ CRLF + lcNotes )
ENDFUNC
* wwDemo :: FileUpload
Nothing fancy here. Everything works as with standard forms except for the call to GetMultipartFile() which retrieves the file and filename. Note that the filename is passed by reference so that the value can be updated by the function.
Uploading Files in the Web Control Framework
The process for Web Control Framework pages is actually quite similar, but it's maybe even easier because the framework automatically handles setting the content type of the form for you. The Web Control framework contains a wwWebFileUpload control which you can place on a form and it automatically manipulates the form to switch the form into multipart mode.
The following example is an image uploader application that allows uploading and immediately displaying the uploaded image in the Web page:
<%@ Page Language="C#"
ID="UploadImage_Page"
GeneratedSourceFile="webcontrols\UploadImage_page.prg"
%>
<%@ Register Assembly="WebConnectionWebControls"
Namespace="Westwind.WebConnection.WebControls"
TagPrefix="ww" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Image Upload Example</title>
<link href="westwind.css" rel="stylesheet" type="text/css" />
</head>
<body style="margin-top:0px;margin-left:0px">
<form id="form1" runat="server">
<h1>Image Upload Example</h1>
<ww:wwWebErrorDisplay runat="server" id="ErrorDisplay" />
<div class="notebox">
This example demonstrates how to upload a file inside from within a Web Form
and retrieve it on the server. Note that the form must be submitted
with enctype="multipart/form-data" in order for uploads to work!
</div>
<div class="containercontent">
Please pick an image to upload:<br />
<ww:wwWebFileUpload ID="FileUpload" runat="server" style="width:400px"/>
<br />
<br />
Leave a note about the file:<br />
<ww:wwWebTextBox ID="txtFileName" runat="server"
TextMode="MultiLine" Width="400px"
/>
<br />
<ww:wwWebButton ID="wwWebButton1" runat="server"
Click="btnSubmit_Click"