Wednesday, April 29, 2009, 5:12:00 PM
A number of people have run into issues with PCI compliance due to a security bulletin that was put out on Web Connection some time ago. The issues in the bulletin have since been addressed in recent versions, but I thought I take the time to reiterate the importance of making sure that your Web Connection applications are secure.
The Security Bulletin Issues
Let’s start by addressing the Security Bulletin issues first.
XSS Attack
This cross site scripting issue has been fixed some time ago. I don’t remember the actual version number but the fix has been in recent versions of Web Connection. If you’re running version 5.x you can upgrade to the latest version, older versions can manually do a quick fix for this particular issue.
The issue is that on a failure request that tries to access a page Web Connection by default returns an error page that looks something like these:
http://www.west-wind.com/wconnect/wc.dll?wwdemo%3Cscript%3Ealert%28%27DANGER%20WILL%20ROBINSON%27%29;%3C/script%3E
http://www.west-wind.com/wconnect/wc.dll?wwdemo%22%3Cscript%3Ealert%28%27DANGER%20WILL%20ROBINSON%27%29;%3C/script%3E
The issue here is that West Wind Web Connection echo’s back the query string value it finds and in earlier versions this value was not properly sanitized. If not sanitized it’s possible to embed script into the URL and that script can execute in the browser.
As mentioned this has been fixed in current versions, so if you create a new project all’s well. You should see something like this:
This properly encodes the offending input and simply echo’s it back. In the actual HTML the text is HtmlEncoded and looks like this:
..type of Request: WWDEMO<SCRIPT>ALERT('DANGER WILL ROBINSON');</SCRIPT>
and so is safe.
However even if you are running the latest version but you have a main application class (ie. MyAppMain.prg) you may still have this vulnerability in place! It’s an easy fix, but you still have to fix it. Find the Process() method in the MyAppMain.prg and the Process method. In it towards the bottom find the OTHERWISE clause and make sure the call the StandardPage() includes EncodeHtml() for encoding the lcParameter:
OTHERWISE
*** Error - No handler available. Create custom
Response=CREATE([WWC_RESPONSESTRING])
Response.StandardPage("Unhandled Request",;
"The server is not setup to handle this type of Request: "+ EncodeHtml(lcParameter))
This basically sanitizes the parameter and ensures it is turned into an HTML string rather than embedded as raw HTML text that can contain script.
There are several other places inside of West Wind Web Connection where similar routing errors echo back content but those locations are internal and have been fixed. If you have a pre-5.0 version of West Wind Web Connection you’ll want to look in wwProcess::RouteRequest (or wwProcess::Process() in older versions).
Note that although this fixes the West Wind Web Connection internal messages, there are still a few other places where this can be problematic, especially in your own code. Specifically some of the West Wind Web Connection demos that strive to show you some of the information available can potentially be used for XSS attacks as well, so on production sites it’s a good idea to remove the wwDemo project from the installation.
This also applies to your own code – anytime you take input from a query string there’s potential that the URL will be hacked and you have to be careful when echo that value back in the user interface. The universal and often tedious remedy for this is to use EncodeHtml() around text displayed to ensure that angle brackets (< >) are properly Html encoded and aren’t interpreted as html and script.
If you are going through PCI compliance especially, make sure you review your application and look for places where user input is DIRECTLY echo’d back. You’d be surprised how many places there actually are in your applications where this can occur.
XSS attacks are tricky because they don’t seem very dangerous and typically they aren’t unless you can force somebody to click on a corrupt link that includes script. The most common target of XSS attacks is to give up cookie and possibly confidential information (the latter is difficult to do, the former quite easy). It takes quite a bit of effort to ensure that all input you receive is sanitized, but that is a responsibility of your application. It has to decide what should be encoded and what shouldn’t.
It pays to think about this while you’re actually developing your applications and anticipate vulnerabilities. It’s much easier to fix at the time of creation rather than after the fact!
Administration Access
By default West Wind Web Connection ships with Administration access wide open, not requiring any security. Specifically this refers to the security setup in wc.ini which determines which account has access to the administration functions. The AdminAccount key in wc.ini controls this and by default it is shipped blank. I have now made a change in the default as of version 5.42 but I suspect this will cause more problems than it solves as people trying out the product for the first time are likely to be struggling with figuring out which login to use.
Anyway, Security is important and any site that goes live should have security enabled. To do so open up wc.ini and set the AdminAccount key:
;*** Account for Admin tasks REQUIRED FOR ADMIN TASKS
;*** NT User Account - The specified user must log in
;*** Any - Any logged in user
;*** - Blank - no Authentication
AdminAccount=rstrahl,megger
You can use any valid Windows user account here or ANY to allow only non-anonymous access to the admin interface. The new default for new projects starting with Version 5.42 is ANY.
Note that this key controls access to the wc.dll admin functions as well as access to the ~wwMaint functions that are the FoxPro based administration links (show and clear logs, reindex system files etc.). These wwMaint process class also reads the security settings from wc.ini, although in wwMaint it’s possible to override the security behavior if you choose. The DLL access is limited to OS Authentication.
Note that several people over the years have mentioned that they thought just removing the Admin page will protect them from anybody finding the wc.dll admin links. THIS IS NOT THE CASE. Anybody that knows the URLs can access the admin links so it is vital that every application you deploy has security set on the administration links in wc.ini!!!
Other Security Items you should implement
First off the help file has a fair bit on Security configuration of West Wind Web Connection here:
http://www.west-wind.com/webconnection/docs?page=_s8w0rmxwf.htm
and more to the point:
http://www.west-wind.com/webconnection/docs/_00716r7og.htm
You can read more detail there but I’ll highlight a couple of additional things that you’ll want to do:
- Set ACL Permissions on Directories
You’ll want to lock down any sensitive or administration directories by removing anonymous access in these folders of your application. Remove the IUSR_ account from these directories. Strip all directory access to the lowest access you really need – for West Wind Web Connection apps this typically means Read/Execute access. Remember West Wind Web Connection runs under either the active account when running in file mode or under the configured DCOM account. The only user that needs rights typically is just that account. So if you need to write files in the Web folder because you’re auto-generating images or reports for later pickup for example, you only need write access for the account West Wind Web Connection runs under.
- Section your Site into Open and Locked Down Areas
On too many occasions I’ve reviewed sites to find that Administrative features are intermixed with application level functionality and usually that’s a really bad idea. If you have administrative tasks that require elevated rights and access to sensitive features, make sure you isolate those features into separate folders and possibly separate West Wind Web Connection Process classes. This makes it much easier to administer security on these areas. Folders can be locked down with OS security and a single process class can handle security all in one place (like wwProcess::OnProcessInit or OnAuthenticate). This makes the security features isolated and maintainable in one or two places.
- Use ScriptMaps – don’t use wc.dll
This isn’t really a security item per se, but it affects site administration. By using script maps you’re not tying yourself to a specific implementation and you get much more control over links and how links are fired. Further script maps allow treating scriptmapped pages just like any other page and respect relative paths, something that wc.dll directly doesn’t do. ScriptMaps can also prevent direct access to admin functionality which further limits the security footprint of your . Additionally IIS 7 doesn’t allow execution of .dll files out of a bin directory any longer (unless you override the filter rule) and direct access to .DLL links requires additional rights configuration in IIS. ScriptMaps provide a safer way to access requests. If you are still calling wc.dll directly consider moving to script maps.
Security in Web application is a serious issue and it isn’t easy. Security is a process not something that you can easily slap on after the fact – thought needs to be given to security issues right from the start.
Thursday, April 09, 2009, 5:31:00 PM
I ran into a nasty problem with a Web Service the other day that expects a GUID as part of its parameters. I’m using the West Wind Web Service Proxy Generator to generate a .NET service proxy for the service for use in Visual FoxPro. The service expects a GUID which in the .NET service proxy is then represented as a System.Guid object.
System.Guid is a value type in .NET which means it’s not based on a class but on a struct. This struct is a ‘by value’ passed object and unless .NET provides custom marshalling for the type, COM access to this type fails. Guid happens to have no custom COM marshaller and so passing it over COM to VFP fails.
A simple example of what doesn’t work are these two methods:
public Guid GetGuid()
{
return Guid.NewGuid();
}
public string SetGuid(Guid guid)
{
return guid.ToString();
}
If create these now through COM Interop there’s no way to call the first method and get a result, and because you effectively can’t create a new instance of a GUID in VFP, no way to even think about calling the second one. So the following sequence of Fox code fails miserably:
o = CREATEOBJECT("westwind.wwDotNetBridge")
loVal = o.GetGuid()
? o.GetSetGuid(loVal)
You end up getting an error: Function argument value, type or count is invalid.
That sucks royally! Especially since there’s really no way around this because no matter how you twist this with direct COM interop there’s no way to create a .NET System.Guid instance in FoxPro. I played around with Reflection and some internal mapping which works at the .NET end but always fails when the guid in anyway is exposed to FoxPro code. Bummer.
Injection
After giving this some more thought and testing a few dead ends I finally managed to find - an admittedly ugly - solution that might be of interest for a host of other types that might not work inside of Visual FoxPro via COM interop.
The concept is based on a façade and call interjection, that relies on indirect calls to the COM server. Some time back I’ve built a DotNet Interop tool for Visual FoxPro called the wwDotNetBridge which is as the name suggests a bridge interface to .NET. Among its features is the ability to create .NET Components directly without having to rely on COM invokation. The library provides non COM-registered activation of .NET components as well as a host of helper methods and functions that allow access to types that typically cannot be accessed over COM interop. In fact only a small percentage of types in .NET are COM accessible and with wwDotNetBridge you can access many more of them directly as well as access arrays more easily, access static properties and methods, access enum values and so on.
Much of what wwDotNetBridge does is accomplished by indirect invokation: So rather than directly operating on the COM instance that VFP has access to an indirect mechanism that uses Reflection inside of .NET to act s a proxy for the FoxPro instance. This has the effect that you have access to much more functionality than VFP has directly against the .NET component.
The upshoot of this process is that this is a controlled process – I have control over parameters that are passed in and return values returned back out. Which brings us to the solution to the problem I mentioned.
The idea is that I can now use indirect calls using InvokeMethod to do parameter and result value fixups and if the value is a GUID it’s converted into custom object provided in wwDotNetBridge that is accessible in VFP:
CLEAR
DO wwDotnetBridge
LOCAL oBridge as wwDotNetBridge
oBridge = CREATEOBJECT("wwDotNetBridge")
*** create our .NET object to call method on
loInst = oBridge.CreateInstance("Westwind.WebConnection.TestType")
* loInst.GetGuid() && fails
*** Call method that returns a GUID: Return a custom object ComGuid
loGuid = oBridge.InvokeMethod(loInst,"GetGuid")
*** Custom object contains string version of Guid
? loGuid.GuidString && Print a a new Guid
*** You can assign to it as a string and it will set an internal GUID
loGuid.GuidString && assign a new Guid
*** Call method that expects a guid with the wrapper object
? oBridge.InvokeMethod(loInst,"SetGuid",loGuid)
*** Create a new Guid with a new Guid value
loGuid = oBridge.CreateInstance("Westwind.WebConnection.ComGuid")
loGuid.New() && another new value
? oBridge.InvokeMethod(loInst,"SetGuid",loGuid)
Notice that the calls to the Guid related methods are implicit using InvokeMethod. InvokeMethod uses Reflection but internally checks parameters and if a GUID (or a couple of other types) are found, fixes up these parameters/result values and turns them into something that VFP can deal with. A few other things I’ve done is turn DataSets into XML automatically and fix up COM SafeArrays that can’t be accessed as byte arrays in .NET to mention a few.
The result above is that in the code above I get not a GUID object returned but an instance of a custom type I created ComGuid which wraps the .NET Guid instance and provide a string based interface to VFP. The simple .NET ComGuid implementation looks like this:
/// <summary>
/// COM wrapper for the .NET Guid control that allows
/// </summary>
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("Westwind.ComGuid")]
public class ComGuid
{
public Guid Guid
{
get { return _Guid; }
set { _Guid = value; }
}
private Guid _Guid = Guid.Empty;
public string GuidString
{
get
{
return this.Guid.ToString();
}
set
{
this.Guid = new Guid(value);
}
}
public void New()
{
this.Guid = Guid.NewGuid();
}
}
This class is a simple wrapper around a GUID instance and allows you to manipulate the Guid instance via strings that you can use in VFP. If you set the string GuidString property it internally updates the Guid instance which when passed back to .NET is used as the actual GUID parameter as long as you’re using InvokeMethod() to do it.
The Guid string format is this and comes from the Guid.ToString() method in .NET:
3484b4dc-841d-408d-8de3-0cfb1b6582bb
The way the intercept works as follows. All methods that set and retrieve values have calls to FixupXXX methods and look something like this:
protected object InvokeMethod_Internal(object Instance, string Method,
params object[] args)
{
object result = ReflectionUtils.CallMethod(Instance, Method, args);
return this.FixupReturnValue(result);
}
Where the actual FixupReturnValue() and FixupParameter() methods look like this:
/// <summary>
/// Fixes up a return value based on its type
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
private object FixupReturnValue(object val)
{
if (val == null)
return null;
// *** Need to figure out a solution for value types
Type type = val.GetType();
if (type == typeof(Guid))
{
ComGuid guid = new ComGuid();
guid.Guid = (Guid) val;
return guid;
}
return val;
}
private object FixupParameter(object val)
{
if (val == null)
return null;
Type type = val.GetType();
// *** Fix up binary SafeArrays into byte[]
if (type.Name == "Byte[*]")
return ConvertObjectToByteArray(val);
if (type == typeof(ComGuid))
return ((ComGuid)val).Guid;
return val;
}
Notice that the FixupParameters fixes up another parameter type – a Binary input from FoxPro (CreateBinary or CAST(x as Q) which also doesn’t work any other way when you pass a binary value from VFP to .NET. These Fixup methods ensure that there’s a point of interception for these types of conversion problems so that FoxPro code can reasonably cleanly call a .NET component with a problem type.
All of this is handled automatically by wwDotNetBridge, but I suspect as time goes on I’ll end up adding more custom ‘conversions’ into these Fixup methods. With these hooks in place it’s likely that just about any kind of type that can be fixed up in some way that it can be consumed and sent from VFP. It’s a sort of custom marshaller.
Although I’m describing this as part of wwDotNetBridge which is part of the West Wind Client Tools you can apply this same functionality in your own components (or create a simple Reflection helper in .NET that you call from your COM calls). I talked about the core concepts of wwDotNetBridge here some time ago.
Friday, February 20, 2009, 12:38:00 AM
A lot of people seem to have run into problems with the update of Web Connection 5.41. The problem is that you may be running the new version of Web Connection and get an error like the following:
Function FileTime() not found
As it turns out FileTime() is indeed a new function in wwUtils.prg in version 5.41 and it is prominently used in the Process class’s RouteRequest() method and because it’s in RouteRequest, if there is a problem with finding this new function you’ll see an error on every hit agains the Web Connection server.
If you’re seeing this error, the problem is – compilation. I’ve seen several messages and a few support calls about this and IN EVERY INSTANCE the issue was resolved by properly recompiling your code.
Let me explain <g>. When you update a Web Connection installation with the latest version it’s absolutely critical that you recompile all of the PRG and VCX files. Why? Because if you just copy in the new PRG files that are distributed with a new update you are updating only the PRG files and not the FXP files that already exist on your machine! Simply put your FXP files may be newer than the PRG files we ship and so the FXP files are not updated to reflect the latest version of the new program files.
There are two ways to update this right:
- Recompile all files
- Compile the application into an EXE and run the EXE
- Make sure CONSOLE.EXE is up to date
Recompile all Files
Personally I rarely run compiled applications so the first option is what I usually work with. In order to do this I recommend you delete all the FXP files in your Web Connection folder and then either manually recompile or just run the application which should then recreate the FXP files when you run.
The following should do the trick from the command window:
CLOSE ALL
CLEAR ALL
DELETE FILE classes\*.fxp
COMPILE classes\*.prg
COMPILE CLASS CLASSES\*.vcx
NOTE: You cannot use the Project Manager and recompile an APP or EXE file to get the FXP files to update. The project manager can build an up to date EXE/APP, but it will not update FXP files on disk. So an explicit compile is required for the PRG files. VCX files are compiled by the project manager since they are self contained and embedded into the EXE
If for some reason this doesn’t do the trick for you, search for any of the Web Connection FXP files in your FoxPro path in different locations.
To help with this process, the next update to Web Connection there will include be a small helper program that you can use to update your application(s) easily. It’s basically a small PRG file that recompiles all the Web Connection files in your application and also copies all Web related dependency files – scripts, styles, Web Controls, updated DLLs – into one Web directory. You can run this script multiple times to update several Web directories with the latest Web related resources .
In the next rev (5.43 or later) you will be able to do:
DO console\UpdateVersion WITH "c:\inetpub\wwwroot\wconnect"
To force an update of the installation in the install folder and the specified Web folder.
Here’s what this script looks like if you’re interested and don’t want to wait for it in the next update:
************************************************************************
* UpdateVersion
****************************************
*** Function: Updates a Web Connection installation rrrby recompiling
*** all Web Connection files in the classes directory and
*** copying Web support files into a Web directory specified.
*** Assume: Web Path must exist
*** Pass: lcWebPath - Optional, File path to a Web Connection Web
*** you need to run this on each Web site to update
************************************************************************
LPARAMETERS lcWebPath
DO wwUtils
IF EMPTY(lcWebPath)
lcWebPath = GETDIR("\","Please find the Web Directory where files are to be copied","Find Web Directory",64+ 16)
IF EMPTY(lcWebPath)
RETURN
ENDIF
ENDIF
*** Must run out of the Web Connection Root directory!
IF !ISDIR("templates")
WAIT WINDOW "This script needs to be run out of the Web Connection Install Folder"
RETURN
ENDIF
IF !ISDIR(lcWebPath)
WAIT WINDOW NOWAIT "Invalid Web Path specified."
RETURN
ENDIF
lcWebPath = ADDBS(lcWebPath)
LOCAL lcWebTemplate
lcWebTemplate = "templates\webTemplate\"
SET PROCEDURE TO
SET CLASSLIB TO
WAIT WINDOW NOWAIT "Compiling files..."
DELETE FILE classes\*.fxp
COMPILE classes\*.prg
COMPILE CLASS CLASSES\*.vcx
DO wwUtils
WAIT WINDOW NOWAIT "Copying files..."
*** wwScriptLibrary is copied from an embedded resource
CopyTree(lcWebTemplate + "scripts\*.*",lcWebPath + "scripts")
CopyTree(lcWebTemplate + "images\*.*",lcWebPath + "images")
LOCAL loException
loException = .NULL.
TRY
*** Rename wc.dll file first then copy: NOTE: requires restart for new dll to actually load
IF FILE(lcWebPath + "bin\wc.dll")
ERASE (lcWebPath + "bin\wc.dll.bak")
RENAME (lcWebPath + "bin\wc.dll") TO (lcWebPath + "bin\wc.dll.bak")
ENDIF
COPY FILE("scripts\wc.dll") TO (lcWebPath + "bin\wc.dll")
IF FILE(lcWebPath + "bin\WebConnectionModule.dll")
ERASE (lcWebPath + "bin\WebConnectionModudule.dll.bak")
RENAME (lcWebPath + "bin\WebConnectionModule.dll") TO (lcWebPath + "bin\WebConnectionModudule.dll.bak")
ENDIF
COPY FILE("scripts\WebConnectionModule.dll") TO (lcWebPath + "bin\WebConnectionModule.dll")
*** Update WebControls on a development machine - won't matter on a runtime box
IF FILE("VisualStudio\WebConnectionWebControls\bin\Debug\WebConnectionWebControls.dll")
COPY FILE ("VisualStudio\WebConnectionWebControls\bin\Debug\WebConnectionWebControls.dll") TO (lcWebPath + "bin\WebConnectionWebControls.dll")
ENDIF
COPY FILE (lcWebTemplate + "westwind.css") TO (lcWebPath + "westwind.css")
WAIT WINDOW NOWAIT "Version Update Complete"
CATCH TO loException
WAIT WINDOW NOWAIT ;
"Web Resource Update Failed" + ;
loException.Message
ENDTRY
RETURN
Since this is just a script you can also copy and update this to add your own logic to handle any special updates you might need to do when a new version comes around, perhaps of your own applicatino.
Run a compiled EXE
The other alternative is to run as an EXE. Compile your project in the project manager and compile it to an exe and run the EXE.
Trust me – it’s Compilation
Several knowledgable people have come at me with this particular issue and I can insure you that the problem is DEFINITELY compilation. If you don’t think so compile the EXE and run it and that should 100% resolve the issue always.
CLEAR ALL
CLOSE ALL
DO wcDemo.exe
Old Version of Console.exe
One other thing that can cause problems is the CONSOLE.EXE which runs when Web Connection starts. There’s a Command=DO WCSTART.PRG in config.fpw, that causes the console to run when Web Connection starts and when it does it loads up it’s internally compiled versions of the various Web Connection libraries. If these versions are out of sync with the current version of Web Connection there can be problems with files not being found.
In fact, it turns out this is what is causing the problem that so many people were seeing in 5.41 – an older version of CONSOLE.EXE was accidentally shipped with Version 5.41. You can re-download version 5.41 which includes an updated file that is in sync with the current version prg files.
Alternately you can force FoxPro to release these procedures by doing:
CLEAR ALL
CLOSE ALL
Do wcDemoMain.prg && use your PRG main
After that you you should again have a clear procedure stack that will reload when your application runs. Using the Web Connection menu will re-run CONSOLE.EXE for many options and may also reload the CONSOLE.EXE files.
Sunday, January 04, 2009, 2:08:00 AM
On December 29th, 2008 Web Connection customers started running into problems with West Wind Web Connection. On the morning of the 29th a number of new messages were starting to pour in relating to a problem with Cookies that no longer appeared to work. Specifically many people were reporting that their Session objects were no longer working properly with Session objects just getting lost and resetting on every hit.
It turns out this is a sleeper bug that dates back to older versions of Web Connection (3 and 4 specifically) and is caused by a hard coded Cookie expiration date in the Web Connection core. This was actually fixed in Web Connection 5 and the newly introduced wwPageResponse class, which correctly uses a relatively offset expiration date for cookies. However, even in Web Connection 5 the old classes still exhibit the old behavior with the hardcoded date and a lot of people (including some of my older code) still rely on the older wwResponse, wwHttpHeader and wwScripting classes which all hardcoded the expiration date.
How do I fix the Problem?
A lot of older Web Connection applications broke as a result of this hardcoded Cookie value which happened to expire on December 28th, 2008. The solution to this problem is quite simple actually and there are a couple of approaches you can take depending on which version of Web Connection you are running.
Update to Version 5.41
The easiest thing to do is update to Web Connection 5.41, which has the problem fixed. If you are a registered user of Web Connection 5, this is a free update so there’s no reason not to install this update. You should have already gotten a notification of the new version along with a note regarding this problem along with download information for the new version (same as all other updates – just change the version numbers to 541).
This version has updated all the relevant Cookie definitions and sets them to relative values in the same way as wwPageResponse has done all along. When you install the new version – as you do with all update installs – make sure you RECOMPILE all the Web Connection source code in the Classes folder including the VCX and PRG files.
Manually update the Code
If you are running older versions and you don’t plan on updating or even if you’re running Web Connection 5, you can make a few small changes to source code to fix the problem as well. The affected classes/methods are:
- wwHttpHeader.AddCookie (wwHttpHeader.prg)
- wwScriptingHttpResponse.AddCookie (wwScripting.prg)
The fix in both places is similar and involves changing the tcExpire variable from a hardcoded string to a relative value:
tcExpire=MimeDateTime(DATETIME() + 94170000,.T.)
Here’s the full implementation of the AddCookie() method in wwHttpHeader:
*********************************************************************
FUNCTION AddCookie(tcCookie, tcValue, tcPath, tcExpire)
*******************************************************
tcCookie=IIF(VARTYPE(tcCookie)="C",tcCookie,"")
tcValue=IIF(VARTYPE(tcValue)="C",tcValue,"")
tcPath=IIF(VARTYPE(tcPath)="C",tcPath,"/")
tcExpire=IIF(VARTYPE(tcExpire)="C",tcExpire,"")
IF UPPER(tcExpire)="NEVER"
tcExpire=MimeDateTime(DATETIME() + 94170000,.T.)
**"Sun, 28-Dec-2008 01:01:01 GMT"
ENDIF
IF !EMPTY(tcExpire)
tcExpire="; expires="+tcExpire
ENDIF
THIS.oHTML.Write([Set-Cookie: ]+tcCookie+[=]+tcValue+;
[; path=]+tcPath+tcExpire+CRLF)
ENDFUNC
* EOF wwHTTPHeader::AddCookie
Please note, that if you’re running a really old version of Web Connection MimeDateTime() won’t exist (it was added sometime late in the 3.x cycle). In that case you will have to set the date to an explicit value as above and set it to a future date. I recommend not putting this date to far in the future (5 years is about right) and making sure that the mime date string is valid (ie. the date and day of the week are correct). Incidentally, lack of a MimeDateTime() function at the time the original code was written was the reason for hardcoding the cookie date in the first place.
Embarrassing
This kind of bug is quite embarrassing actually – hardcoding values that are bound to change, like dates, is a really bad development practice. Actually I remember precisely how this came about many many years ago. About 10 years ago apparently I was working on that piece of code that set the expiration date and I had issues with certain browsers (Netscape 4.0 specifically) not accepting a cookie expiration date much further in the future. So I ended up experimenting around with different values to find the top end of the range that WAS supported which happened to be somewhere around 10 years (although not exactly), which is the value I set at the time. I figured in 10 years I’d be getting back to this for sure, if the product would even last that long. Well,here we are 10 years later and indeed we’re seeing the fruits of my failure now.
The problem was made worse though by the introduction of a new wwPageResponse class that appropriately fixed the problem going forward by using a sliding date setting, so I never even gave a second thought to the older classes. In fact while a lot of thought went into the refactoring that resulted in the wwPageResponse class, none of the old code got much in the way of code review which is why this problem occurred.
Keeping up to Date
If you’re running an older version of Web Connection you might want to consider updating to the latest version. It’s issues like this as well as security related issues related to the operating system that can often bite you and the currently developed version often fixes many small issues that get addressed transparently in maintenance releases so you never even end up seeing a problem in the first place. Unfortunately this particular bug slipped through the net of checks because it’s in deprecated code, but it’s been updated immediately as soon as the problem became clear and we notified all customers of the problem along with the notification that the new version was available to download with a fix.
Older versions see no updates and inline fixes of any sort so any fixes will have to be applied manually. This isn’t meant as a sales pitch but if you work with a live and active application it might be good to know there’s an active support path that addresses issues as soon as possible.
Sunday, October 12, 2008, 11:54:00 PM
Visual Studio provides some fairly decent Intellisense support in Visual Studio and if you’re using Web Connection’s Web Control Framework you can take advantage of Intellisense quite easily.
Anytime you have <script> reference in a page VS.NET will provide Intellisense for the referenced script so:
<head>
<title>Web Connection Ajax Chat</title>
<link href="../wconnect/westwind.css" rel="stylesheet" type="text/css" />
<script src="../scripts/jquery.js " type="text/javascript"></script>
</head>
<body>
Provides Intellisense for the referenced library in source code. This works for any HTML style pages including Web Control Framework pages.
Sometimes you may prefer using script inclusion in actual Javascript only files rather than HTML pages. So if I have a library like my ww.jquery.js file and I want to get Intellisense for jQuery I can do:
/// <reference path="jquery.js" />
this.HttpClient = function(opt) {…}
at the very top of the .js file to force Intellisense to be available. The path specified is relative from the current file or you can use ~/scripts/jquery.intellisense.js for a more explicit path.
jQuery Intellisense
By default jQuery doesn’t work very well with Visual Studio Intellisense, so you may be surprised to hear that you can get pretty good jQuery Intellisense support, although it requires a few tweaks to your source file. Apparently Microsoft will be updating Intellisense support for jQuery drastically now that jQuery will be officially included with .NET and Visual Studio.
Until that happens though there are two approaches you can take to get jQuery Intellisense.
Markup jQuery.js with Intellisense specific comments
I’ve used this route before and if you’re using a recent version of Web Connection you already have a copy of the basic comment markup in the jQuery.js file distributed in the /scripts folder of a new application. The idea is that by adding Intellisense comment syntax to jQuery and only marking up the jQuery function you get first level Intellisense on any jQuery selector operations:
var jQuery = window.jQuery = window.$ = function( selector, context ) {
/// <summary>The jQuery object is actually just the init constructor 'enhanced'</summary>
/// <param name="selector" type="var">Document selector.
/// (examples: "Element","#Id",".cssClass","#divMessage,#divError",DomElement,jQueryObject)
/// </param>
/// <param name="context" type="object">Object scope of any code executed with jQuery functions</param>
/// <returns type="jQuery" />
return new jQuery.fn.init( selector, context );
};
This alone will let you type $(). and get a list of all jQuery functions available, which provides a good baseline. However, unless you mark up all the wrapped set functions Intellisense is only 1 level deep: The above only works for $(). but not for $().append(). – the second level doesn’t provide anything because append() is not marked up.
The version of jQuery.js that ships with Web Conection is marked up with this by default so as long as jQuery.js is referenced you should get basic Intellisense 'out of the box'.
Using an Intellisense.js specific Temporary Include
The above works well enough but it’s intrusive as it changes the original file and requires changing it with each update of jQuery. An alternative is to use a separate file that includes only the Intellisense and is a design time only file and hidden at runtime.
The first thing that’s needed for this is the script file which you can find here:
http://www.infobasis.com/sandpit/jQuery-Intellisense/
Copy this content into a file and name it jQuery.intellisense.js and store it in the same folder as jQuery – in ~/scripts for Web Connection.
Then you can do the following to include it in your page:
<html>
<head>
<title>Repeaters and Editing</title>
<link href="../westwind.css" rel="stylesheet" type="text/css" />
<script src="../scripts/jquery.min.js" type="text/javascript"></script>
<script src="../scripts/ww.jquery.min.js" type="text/javascript"></script>
<ww:wwWebLiteral ID="WwWebLiteral1" runat="server" Visible="false">
<script src="../scripts/jquery.intellisense.js" type="text/javascript"></script>
<script src="../scripts/ww.jquery.js" type="text/javascript"></script>
</ww:wwWebLiteral>
</head>
<body>
Notice that there are two sets of script references in the page: The actual scripts that will be used on the page and the scripts used for Intellisense which are stored inside of the Literal controls which is set to be invisible. The idea is that VS sees the scripts but at runtime the code is not rendered.
What this means is that you get Intellisense at design time, and only the embedded scripts at runtime.
The above trick with the Literal control also works if you have Web Connection automatically embed scripts into the page. For example, if you use wwAjaxMethodCallback control by default Web Connection will automatically embed scripts into the page so there are effectively no script embeds in the page. But you can add the literal control along with any scripts that you want Intellisense for which is great.
In fact, Web Connection now adds this literal to the default page template, but note that the scripts are NOT automatically added to the page – only when controls are used that rely on them and then only if you don’t override or remote the script auto loading.
Getting Intellisense with jQuery is very useful. Although jQuery’s feature set is small enough to be easy rememberable, there are still a host of functions I often forget about and Intellisense makes them easier to use. Both of these approaches can be a big productivity enhancement.
The downside of the jQuery.Intellisense.js file is that it won’t let you see other plug-ins. So if you’re using Web Connection I still recommend the former approach for now of referencing the marked up jquery.js file so you can see all the jQuery plug-ins that the Web Connection client library provides (in ww.jquery.js).
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"
Text="Upload" width="80" />
<hr />
<ww:wwWebImage runat="server" id="imgPreview"
style="float: left; margin-right: 10px;" />
<small>
<ww:wwWebLabel runat="server" ID="lblCaption" />
</small>
</div>
</form>
</body>
</html>
Note that the form is NOT set up with enctype="multipart/form-data" although if you do add that manually that will work too. It's just not required here. Note that the upload control MUST live inside of the <form runat="server"> tag block.
Other than that it's just a normal input form. To capture the file now you can use code like the following in the button submit:
************************************************************************
* btnSubmit_Click
****************************************
*** Function:
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION btnSubmit_Click()
LOCAL lcFileName, lcContent, lcNotes
*** Pick up content and filename
lcFilename = ""
lcContent = Request.GetMultiPartFile("FileUpload",@lcFileName)
lcNotes = this.txtFileName.Text
lcExt = LOWER(JUSTEXT(lcFileName))
IF EMPTY(lcExt) OR NOT lcExt $ "jpg|jpeg|png|gif|tif|bmp"
this.ErrorDisplay.ShowError("Invalid Image type uploaded")
RETURN
ENDIF
IF LEN(lcContent) > 500000
this.ErrorDisplay.ShowError("Image uploaded is too large.")
RETURN
ENDIF
*** Generate output filename and full output path
*** Note reading from demo's cHtmlPagePath from .ini file
lcSaveFileName = "_timg_" + lcFileName
lcSaveFullPath = Server.oConfig.owwDemo.cHtmlPagePath +;
"temp\" + lcSaveFileName
*** Dump the file content string to a file
STRTOFILE( lcContent, lcSaveFullPath )
*** Assign the Web based file path to the image to display
this.imgPreview.ImageUrl = "~/temp/" + lcSaveFileName
this.lblCaption.text = lcNotes + ;
"<hr><i>" + lcFileName + "<br />" + ;
"Size: " + TRANS(LEN(lcContent)) + " bytes</i>"
*** Clean up files that are older than 120 seconds
DeleteFiles("..\temp\_timg*.*",120)
ENDFUNC
* btnsubmit_Click()
This code is more complete than the last example, but it basically picks up the uploaded file by its form variable name in this case FileUpload. The function returns the file content as a binary string. This output can then simply be written to disk after ensuring that it's of the right type.
In this case the file is written to disk in a Web directory and then displayed in an image control, whose ImageUrl is set dynamically with the new name of the file. Obviously you may want to do something different with this file like store it in a database or store it in some other dir and never display it.
If you do chose the file approach to dump to disk and display the image remember that you'll need to clean up the generated files as the filenames are unique as to avoid name overlap. The DeleteFiles() function from wwUtils makes this task very easy – it simply deletes files that are older than a certain number of seconds which serves as a brute force cleanup routine for 'temporary' files.
Looking over this code it also occurred to me that it'd be useful to have a method on the upload control that can retrieve the file and filename. This would avoid having to know the POST variable name (which may be mangled due to naming containers). So I added a new method to wwWebFileUpload called .GetUploadedFile(@lcFileName) that basically makes a call through the Request.GetMultipartFile(). While no easier than the Request method, the method on the control should make it easier to discover how to actually pick up the file once it's been uploaded. With it the code will change to:
*** Pick up content and filename
lcFilename = ""
lcContent = this.FileUpload.GetUploadedFile(@lcFileName)
lcNotes = this.txtFileName.Text
which is a little cleaner and certainly easier to discover.
There you have it. Nothing difficult about this process, but it is a little different for the two mechanisms.
© Rick Strahl, West Wind Technologies, 2004 - 2009