Tuesday, August 17, 2010, 12:00:00 AM
Although the AJAX features in Web Connection are primarily geared at the Web Control Framework which makes working with AJAX very easy, you can also use the various AJAX features - and JSON callbacks in particular - with plain old wwProcess methods.
In particular there's a wwJsonService class which is basically an RPC JSON service implementation that can be very easily hooked up to any single wwProcess method. The service class lets you route client requests to a specific method of an object that you specify in the service setup. The class handles all input parameter conversion, the actual method call, error handling and returning of a JSON string response.
You can use the wwJsonService features in conjunction with the ww.jquery.js client library, but it's not required. Because wwJsonService takes input in the form of AjaxMethodCallback style parameters, a raw JSON Post object or no particular POST format at all it's up to you to decide how to pass parameters.
Implementing a JSON Service Handler in a Process Method
The first step is to set up the service on the server. A 'service' in this context is really nothing more than an instance of wwJsonService plus a target object on which methods are executed. I recommend you use a separate object for the actual method implementations, but you can also point at THIS which here would be the wwProcess subclass.
Here's an example process method implementation which is pretty much cut and paste except for the object instance that holds the actual methods to call:
FUNCTION JsonCallbacks()
lotarget= CREATEOBJECT("AjaxCallbacks")
loService = CREATEOBJECT("wwJsonService")
Response.ContentType = "application/x-javascript"
lcJSON = loService.CallMethod(Request,loTarget)
Response.Write( lcJSON )
Response.End()
RETURN
ENDFUNC
That's all there's to the service implementation. You create an instance of wwJsonService and you then call the CallMethod() method to actual route a request to the service handler. The service is passed a Web Connection Request object, the target that will handle the method calls - our AjaxCallbacks instance in this case.
Implementing the Service Handler Class
Above I'm using an AjaxCallbacks class to handle the requests, so we need to implement this class. The class is nothing special - it doesn't need to inherit from anything and simply should implement methods that are called from Ajax callbacks. Depending on how you plan on calling the methods the parameter signatures may vary. If you use the ww.jquery.js 'method' calling features you can pass any number of parameters from the client to each method. If you pass a raw JSON objects as a POST object you can accept exactly one parameter. If you don't want to pass anything or just pass up free form POST data don't accept any parameters and simply read the POST data using Request.Form.
Here's an example implementation of a JSON callback handler class:
DEFINE CLASS AjaxCallbacks AS Custom
FUNCTION Helloworld(lcName)
lcName = lcName.Name
RETURN "Hello " + lcName + ". Time is: " + TIME()
FUNCTION GetServerTime()
RETURN DATETIME()
FUNCTION HelloWorldObject(lcName, loResult)
loObject = CREATEOBJECT("EMPTY")
ADDPROPERTY(loObject,"x",10)
ADDPROPERTY(loObject,"y",15)
ADDPROPERTY(loObject,"Name",loResult.Name + CRLF + loResult.Address.Street)
ADDPROPERTY(loObject,"date",DATETIME())
RETURN loObject
FUNCTION GetCustomerList
SELECT Company,CareOf,Pk FROM TT_Cust ;
ORDER BY COMPANY INTO CURSOR TQUERY
RETURN "cursor:TQuery"
FUNCTION GetStockQuote(lcSymbol)
LOCAL loServer as YahooStockServer
loServer = CREATEOBJECT("YahooStockServer")
LOCAL loQUote as YahooStockQuote
loQuote = loServer.GetStockQuote(lcSymbol)
IF ISNULL(loQuote)
ERROR loQuote.cErrorMsg
ENDIF
RETURN loQuote
ENDFUNC
ENDDEFINE
This example is provided as part of wwDemo so you can experiment around with this. As you can see these methods just are plain old methods that take input parameters and return values. A number of different types of values are returned and the JSON encoding preserves the types back to the client.
The service is now ready to receive requests. In this case, any request to JsonCallbacks.wwd will now route to this service.
Calling the Service from JavaScript
There are lot of ways that you can call this service now. The easiest is by using the
ajaxCallMethod function in ww.jquery.js. In order to use ww.jquery.js
Then inside of a script tag of the page you can now call the service. In this case I'm going to call the GetStockQuote method which returns an object:
ajaxCallmethod() is a quick and dirty wrapper around the AjaxCallbackMethod object that assigns the URL and input parameters and fixes them up in the way that the JSON service expects it. You pass in the Url to call (our ProcessMethod JsonCallbacks.wwd in this case), the method in the service, and an array of parameters (or []) for no parameters. Finally there's a callback function that is called with the result data on success or an error function that is called on error (onPageError is a stock method that shows an alert box with the error).
But you don't need to use this high level functionality. If you'd rather just use plain old jQuery you can do that too:
function getStockQuotes() {
var symbols = { quote1: "MSFT",
quote2: "LDK",
quote3: "INTC"
};
$.ajax({ url: "jsonCallbacks.wwd?Method=GetStockQuotes",
type: "POST",
dataType: "json",
data: symbols,
success: function(quotes) {
var output = "";
for (var i = 0; i < quotes.length; i++) {
var quote = quotes[i];
output += quote.company + " (" + quote.lastprice + ")
";
}
$("#divStockResult").addClass("errordisplay").html(output).fadeIn();
},
error: onPageError
});
return;
}
This code use the static jQuery .ajax() function. There are other methods (getJSON(), post() etc.) but ajax is the only one that gives enough control of callbacks to pass and retrieve a JSON response. Not I'm using dataType json to automaticall parse the result from JSON into an object, and I'm passing the symbols as plain POST data (jQuery converts objects into POST key value pairs).
Here's the FoxPro method code on the server in the AjaxCallbacks class:
FUNCTION GetStockQuotes(loSymbolList)
LOCAL loServer as YahooStockServer
loServer = CREATEOBJECT("YahooStockServer")
loQuotes = CREATEOBJECT("wwCollection")
FOR lnX = 1 TO 99
lcQuote = Request.Form( "quote" + TRANSFORM(lnX) )
IF EMPTY(lcQuote)
EXIT
ENDIF
loQuote = loServer.GetStockQuote(lcQuote)
IF ISNULL(loQuote)
CONTINUE
ENDIF
loQuotes.Add(loQuote)
ENDFOR
RETURN loQuotes
ENDFUNC
This code picks up the symbols sent from the client as POST data one value at a time. The value is then used to look up a stock quote. It's added to a collection which is then turned into a JSON array and returned back to the client.
Tuesday, April 20, 2010, 12:29:00 PM
On a few occasions I’ve needed to be able to play and control playback of media files – especially music files. There are a number of different ways to do this.
The simplest way to simply play a media file of any kind is by using the ShellExecute API:
gourl("C:\my music\Uploads\Anthrax\Persistence Of Time\08 Got The Time.mp3")
where GoUrl() is my trusted ShellApi wrapper function:
****************************************************
FUNCTION GoUrl
******************
*** Author: Rick Strahl
*** (c) West Wind Technologies, 1996
*** Contact: rstrahl@west-wind.com
*** Modified: 03/14/96
*** Function: Starts associated Web Browser
*** and goes to the specified URL.
*** If Browser is already open it
*** reloads the page.
*** Assume: Works only on Win95 and NT 4.0
*** Pass: tcUrl - The URL of the site or
*** HTML page to bring up
*** in the Browser
*** Return: 2 - Bad Association (invalid URL)
*** 31 - No application association
*** 29 - Failure to load application
*** 30 - Application is busy
***
*** Values over 32 indicate success
*** and return an instance handle for
*** the application started (the browser)
****************************************************
LPARAMETERS tcUrl, tcAction, tcDirectory, tcParms
IF EMPTY(tcUrl)
RETURN -1
ENDIF
IF EMPTY(tcAction)
tcAction = "OPEN"
ENDIF
IF EMPTY(tcDirectory)
tcDirectory = SYS(2023)
ENDIF
DECLARE INTEGER ShellExecute ;
IN SHELL32.dll ;
INTEGER nWinHandle,;
STRING cOperation,;
STRING cFileName,;
STRING cParameters,;
STRING cDirectory,;
INTEGER nShowWindow
IF EMPTY(tcParms)
tcParms = ""
ENDIF
DECLARE INTEGER FindWindow ;
IN WIN32API ;
STRING cNull,STRING cWinName
RETURN ShellExecute(FindWindow(0,_SCREEN.caption),;
tcAction,tcUrl,;
tcParms,tcDirectory,1)
This works nicely in bringing up Media Player (or whatever your default media player is for a given media type) and immediately playing that content. The downside of this very simple approach is that you have no control over the played media – the content starts playing but you have no connection to the player in any way so you can’t start and stop the content under program control. The player is also always visible.
More Control with Media Player Automation
If you want more control over playing media, like the ability to start and stop the content or by creating full custom playlists you can use Media Player automation. This is a little more work but it gives you full control over the process and it’s reasonably lightweight and very responsive even though it’s working through the full Media Player APIs of Windows.
Here’s a simple example that starts and stops two MP3 files:
LOCAL loPlayer as WMPlayer.OCX
loPlayer = CREATEOBJECT("WMPlayer.OCX")
loItems = loPlayer.MediaCollection
loSong= loItems.add("C:\my music\Uploads\Anthrax\Persistence Of Time\08 Got The Time.mp3")
LOCAL loPlayList as WMPlayer.IWMPPlaylist
loPlayList = loPlayer.currentPLayList
loPlayList.AppendItem(loSong)
loPlayer.Controls.PLay()
WAIT WINDOW "Press any key to stop"
loPlayer.Controls.Stop()
*** Clear the playlist for the next song
loPlayList.clear()
*** Add another song
loSong = loItems.add("C:\my music\Uploads\Anthrax\Persistence Of Time\02 Blood.mp3")
loPlayList.AppendItem(loSong)
loPlayer.Controls.Play()
WAIT WINDOW "press any key to stop"
loPlayer.Controls.Stop()
There are many more options on the Media Player control – you can set including controlling the player itself (like volume, display options etc.) as well as full control over the playlist management. It’s quite nice to be able to control all of this with very little code. Unfortunately the documentation for this stuff can’t be found anywhere online – the best option I found was to use Intellisense and pick through the methods and properties that way. The only way to get full documentation is through the SDK docs.
Posting this mainly as a note to self, since I’ve researched this a few times over and over again when I needed as I forgot to write it down. Next time I won’t have to right? <s>
Tuesday, December 29, 2009, 12:00:00 AM
In Web Connection 5.x and later a new server property called lUnattendedComMode was introduced. This flag controls how user interface interaction occurs by setting SYS(2335,0). This command is described in the FoxPro help file as follows:
Enables or disables modal states for distributable Visual FoxPro .exe automation servers.
This is a rather desirable feature for a server application as it helps avoid accidentally popping up a MessageBox or Wait Window or other UI construct that has the potential to hang your server. It also forces VFP to surpress a few of the dialogs that can’t be handled via error handling – specifically file/table open dialog boxes which are untrappable errors if you don’t use SYS(2335,0).
The lUnattendedMode property is nothing particularily special – you could always in the past do this yourself manually by specifying SYS(2335,0) in code somewhere in your server startup code.
However, there are a few problems with this approach and a few things you need to be aware of. First off Web Connection internally has a few places where WAIT WINDOW TIMEOUT or WAIT WINDOW NOWAIT is called, and due to an unfortunate decision to include WAIT WINDOW in the SYS(2335,0) controlled ‘modal states’ that code can fail. Most of these are present in the server’s user interface – the Web Connection status form. But there are also a few places in the demos and sample applications that also use WAIT WINDOW which also breaks.
Your own code that uses WAIT WINDOW will have a similar fate: The commands are simply treated as errors and if you have debug mode off will cause the error to force an error page.
Changes in Web Connection 5.51
In Web Connection 5.51 a few changes have been made to make the unattended mode a little less restrictive. In prior versions SYS(2335,0) was applied during the server’s startup code after the user called OnInit() call. This was meant to capture startup errors and throw exceptions there if something goes wrong.
This does exactly what it should, but the result is not really desirable:
Unable to load servers.
The application COM servers could not be loaded:
Creating an instance of the COM component with CLSID {17F10C95-B12E-4EF2-A062-9ABC85168AC2} from the IClassFactory failed due to the following error: 80004005.
Message generated by Web Connection IIS Connector Module
The problem is that both OnInit() and OnLoad() are effectively fired during the COM server’s instantiation sequence which means if an error occurs during this phase the server instance is never created. Also unfortunately the COM error info is not sent back to the COM client in the ISAPI DLL or Web Connection .NET Module. The result is the above which is a generic COM error message that COM object creation fails which is not terribly useful.
Unfortunately there’s not much that can be done to improve this process precisely because the object is not actually instantiated yet. For this reason loading SYS(2335,0) in OnInit or OnLoad() in general is not a great idea.
So in Web Connection 5.51 lUnattendedComMode now sets SYS(2335,0) around individual calls by wrapping the ProcessHit() method that starts off requests and then setting SYS(2335,1) when each request ends. This ensures that the unattended mode is only applied against process methods which are generally much less likely to have any UI related code in them.
This also allows the Web Connection server to keep its ability to still display some user interface functionality from the Web Connection status form. There are a handful of Wait Window Routines that are used to let you know that the log file was cleared, settings saved etc.
This also means that when the COM server launches errors now throw whatever errors occur in the FoxPro code – this can in certain circumstances mean hanging situations like file open dialogs and other failures. The advantage while debugging though is that you can see these errors popup if you’re running the COM Servers interactively. This can be tremendously helpful in debugging server startup as I found out today on a support call :-}.
If for whatever you reason you want to surpress modal interfaces and errors during startup as well you can still do that by setting SYS(2335,0) in startup code as always and that will cointinue to work just fine.
Web Connection 5.51 also cleans up a bunch of the WAIT WINDOWs in the samples. Most of these were silly and simply there to provide some feedback. Since that’s probably a bad example usage those are all being removed and I’m running the server in test.
In your own apps if you have situations where there is some UI operation required that you can’t remove you can always manually wrap the code with:
lnOld2335 = lnSys(2335)
SYS(2335,1) && turn it off
WAIT WINDOW TIMEOUT 1 "This operation is REQUIRE bub!"
SYS(2335,lnOld2335) && turn it back
SYS(2335) is a very efficient call in VFP so there’s no perf penalty for doing this sort of thing.
If you haven’t thought about unattended mode and you are running in COM mode, be sure to check it out – it’s a highly useful feature that can help trap a few errors that are otherwise untrappable in VFP – most commonly the File/Table open dialog boxes. To do this flip the UnattendedMode switch to On. in your YourApp.ini file and make sure that you have the error logging enabled so you can watch for potential failures in the log – those errors should tell you if you have any missed modal operations in your app. You might also want to use the CodeReferences and search for any WAIT WINDOW code in your project in case you’ve been careless with them as I’ve been…
As a final note: wwServer::lUnattendedComMode is totally optional. If you prefer to take your chances with modal interfaces that's absolutely supported since EXE COM servers are fully UI capable.
Thursday, December 10, 2009, 1:00:00 AM
Path management in Web Applications is critical and it’s especially tricky if you’re dealing with applications that display content out of many sub-directories. Most of my applicatinos have at least a root folder (duh!) and usually an admin folder that pages or scripts run out of.
It seems easy enough to reference images, CSS and other resources simply with page-relative links like:
<img src="images/help.gif" alt="Get Help" />
This works just fine. But in some situations and especially in situations when you build some sort of reusable components like a User Control or Server Control, a reused script or template it’s not quite so easy to determine a relative path.
Imagine for a second that you have a user control that has a reference to the same image above, but you now want to embed that user control into a page in the root folder, and into another page in a subdirectory. In the root folder the above Image ref works just fine, but in the subfolder – not so much. In the subfolder you’d need:
<img src="../images/help.gif" alt="Get Help" />
But how do you consolidate this?
Root-Relative Paths
Web Connection Web Controls – which is based on the ASP.NET concept – supports a workaround for this issue by allowing root-relative paths to be used for URLs that are specified on Web Controls.
Root relative paths allow you to create location independent urls that work regardless from where within the application they are called. This means you can use a url with root-relative path syntax and get consistent results in any directory of the application which makes the url portable – easy to copy and/or use in other pages without changes. Web Connection supports root-relative paths in various ways. Here’s how.
For example, you can use an image control instead of the raw HTML image tag to solve the problem above like this:
<ww:wwWebImage runat="server" ID="imgHelp" ImageUrl= "~/images/help.gif" />
Notice the ~/images/help.gif path which is effectively means:
Use the applications base Virtual Path (something like /wconnect) instead of the ~/ prefix and then append the rest of the URL to it.
The end result of this is that you can simply specify ~/images/help.gif from any directory of the application and it will always generate /wconnect/images/help.gif regardless of the folder you’re in.
Root-Relative Paths in Web Connection
Root relative paths can be used in several ways in Web Connection
- Native Web Control Properties
All native Web Connection Web Controls and custom URL properties on them support root-relative paths so ImageUrl on the image control, NavigateUrl on HyperLink and button controls etc. all support the ~/ url syntax automatically. All custom controls implemented by third parties should also support this functionality for all URL attributes for consistency.
- Manually via Control.ResolveUrl() and Process.ResolveUrl()
You can manually fix up paths in code using either Control.ResolveUrl("~/images/help.gif") or Process.ResolveUrl("~/images/help.gif"). Internally controls call these APIs to fix up paths and you can also call them from within code any time to Resolve a url.
- Automatic Fixup of generated HTML that contains ~/ attributes (New Feature in 5.51)
Web Connection 5.51 and later also automatically post processing HTML output and replaces any occurrence of ~/ paths at the beginning of an attribute string (ie. = "~/) and expands the full URL if the output is of content type text/html and a Process object is in scope. This is fully automatic now and works even on client controls: <script src= "~/scripts/jquery.js" type="text/javascript"></script> will now automatically transform into /wconnect/scripts/jquery.js.
I’m fairly excited about this latter feature because you can now apply root-relative paths everywhere. As a bonus Visual Studio understands root-relative paths even in plain URLs and so properly includes file references for rendering in the designer using this syntax for Intellisense support of CSS and script Intellisense for JavaScript.
How does Url Resolution Work in Web Connection?
As mentioned url resolution happens in wwProcess::ResolveUrl(). There’s also Control.ResolveUrl but it just defers to Process.ResolveUrl() internally. To understand how it works it’s probably easiest to look at the code which is actually super simple:
************************************************************************
* wwProcess :: ResolveUrl
****************************************
FUNCTION ResolveUrl(lcUrl)
IF lcUrl != "~"
RETURN lcURL
ENDIF
RETURN STRTRAN(Process.cUrlBasePath + SUBSTR(lcUrl,2),"//","/")
ENDFUNC
* wwProcess :: ResolveUrl
The method simply looks at the URL passed and checks for the leading tilde (~) and if it finds one replaces it with the Process.cUrlBasePath.
cUrlBasePath is the tricky issue in this functionality, because there’s nothing in the Web Server request that can easily translate the current request url to a URL base path automatically just based on the IIS request returned. Unlike ASP.NET which has a Request.ApplicationPath property which returns something like /wconnect, raw ISAPI requests do not receive this information.
This means that Web Connection requires an explicit approach to figure out the path and the way this is done by using a configuration setting that is defined on the Process class’s configuration object.
************************************************************************
* wwProcess :: GetUrlBasePath
****************************************
*** Function: Method responsible for establishing the base path
*** for this application.
************************************************************************
FUNCTION GetUrlBasePath()
IF !EMPTY(THIS.cUrlBasePath)
RETURN this.cUrlBasePath
ENDIF
TRY
THIS.oConfig = EVALUATE("THIS.oServer.oConfig.o" + this.Class)
THIS.cUrlBasePath = THIS.oConfig.cVirtualPath
CATCH
ENDTRY
RETURN THIS.cUrlbasePath
ENDFUNC
* wwProcess :: GetUrlBasePath
Assuming we want to retrieve the wwDemo process class configuration we’d access:
THIS.oServer.oConfig.owwDemo.cVirtualPath
Where does this value come from? If you recall Web Connection by default creates a configuration class for each Process class it creates with the same name as the process class prefixed by an o. The wwServer instance has a master configuration object (this.oServer.oConfig) and this config object in turn holds configuration settings for each process class – in this case owwDemo. Each of these classes has a default set of properties of which cVirtualPath is one of them.
To put this into perspective the value can be found in the configuration file (wcdemo.ini):
[Wwdemo]
Datapath=C:\WWAPPS\WC3\wwDemo\
Htmlpagepath=c:\westwind\wconnect\
Virtualpath=/wconnect/
Which maps to the class defined in wcdemomain.prg:
DEFINE CLASS wwDemoConfig as RELATION
cHTMLPagePath = "d:\westwind\wconnect\"
cDATAPath = ".\wwDemo\"
cVirtualPath = "/wconnect/"
ENDDEFINE
This is where the value comes from. The idea is that as long as the cVirtualPath is set properly in the config file the value will be picked up by GetUrlBasePath() and can then be used by ResolveUrl.
Yeah, this is a little on the complex side, but I haven’t been able to find a better way to resolve the base path than manually specifying it. FWIW, all of this should be automatic – the only thing you have to do as a developer is change the cVirtualPath if it changes. The New Project and Process Wizards hook all of this up for you automatically.
Automatic URL Resolution for raw HTML content – A new Feature in Web Connection 5.51
As mentioned earlier there’s a new feature in 5.51 that automatically looks at HTML output generated with the wwPageResponse class and automatically expands any URLs that contain ~/ at the beginning. This is fully automatic and doesn’t require any code changes on your part, you just need to make sure that your application is using the wwPageResponse class which is the default for Web Connection. If you’re using the Web Control framework you’re already using this class, but you can also ensure that this happens by using the wwPageResponse or wwPageResponse40.
This is the default for new projects so nothing needs to be done for new projects. Old projects that explicitly use one of the old Response classes (wwResponseFile, wwResponseString) do not get this functionality because they are not guaranteed to be cached in memory first.
Web Connection basically adds this simple block of code into the wwPageResponse::Render method:
*** Fix up ~/ paths with UrlBasePath
IF THIS.contentType = "text/html" AND VARTYPE(Process) = "O"
lcBasePath = Process.GetUrlBasePath()
IF !EMPTY(lcBasePath)
this.cOutput = STRTRAN(this.cOutput,[= "~/],[="] + Process.cUrlBasePath)
ENDIF
ENDIF
Since STRTRAN() in VFP is a blazing fast operation even on large strings this doesn’t add any significant overhead and so this process has been integrated without any noticable performance penalty.
If you haven’t looked at root-relative paths before be sure to check them out – they can make life a lot easier when building any links that need to live in multiple pages whether its code in user controls, custom server controls or just code that you like to cut and paste between different pages.
Sunday, September 27, 2009, 5:51:00 PM
Updated Web Connection Web Control Framework Training Videos
Over the last week I’ve finally updated the West Wind Web Connection Web Control Framework training videos to reflect changes to the Web Control framework in recent versions. The original videos were getting quite dated as they were actually created prior to Web Connection 5.0 release in late stages of Beta. In addition there had been some changes to the way pages are laid out and the default ‘ASP.NET headers’ are defined in the page which were a result of the Visual Studio 2008 release which broke the initial approach that Web Connection 5.0 used. The new videos can also be watched online with an option to download them locally.
The latest videos are fully up to date with Web Connection 5.50 and reflect the current state of the art <s>. The new videos are also a little bit longer and produced in higher video and audio quality so they should be a little more pleasant to watch and listen to I hope. The extra length of the videos is due to a little bit more in depth discussion of various steps in the process rather than just demonstrating features – I hope this will provide more information to would-be developers and isn’t too overwhelming but some of the feedback I got from the old videos was in the form of a lot of questions like why does x happen when you do this. So the new videos I take a little time to discuss some of these issues with more detail.
The videos also take a slightly different approach in that they don’t exclusively deal with Drag And Drop of controls and property settings, but rather also demonstrate using the HTML markup editor for laying out HTML and taking advantage of CSS based layout in lieu of designer created tags. I see this as an important point to make for Web Development in general and if there’s one thing that I would urge any budding Web developer to look into it’s getting familiar with CSS based HTML layout. (if you’re new get anyting from Eric Meyer: Reference or Practical Projects). It may seem like a lot to take in but trust me the biggest time saver is having some grounding in CSS concepts to make HTML do to your bidding.
There's also one new video which is an Overview of the Web Control Framework. Unlike the other ‘hands on’ videos, this video is mostly slides and discussion along with a handful of short demonstrations that discuss the Web Control framework architecture and design goals. This should fill in some of the questions for those folks who don’t just want to know how it use the framework but how and why.
The result is that the videos are 20-30% longer than the original videos, but they also cover a few additional features that the old videos didn’t cover. BTW, one trick if you’re watching these videos in Media Player (if you downloaded them) is to run them at 150% speed. You can get through them a lot faster especially if you are just watching and not ‘playing along’.
These videos are recorded live – that is they are done in 1 or 2 takes at most. They aren’t meant as marketing videos that show tons of features, but rather at a getting started tutorial and are meant to show the process of building applications in a step by step way. They are by no means comprehensive in showing all the features or all functionality/setup and steps, but rather are supposed to give you a realistic feel of what the development process with the Web Control Framework looks like.
I’m interested in feedback, although I can’t promise that I’ll be changing them until I get the inspiration to do another run on them which probably won’t be soon – the process to produce these live recorded videos is quite lengthy as it usually takes me 2 or 3 passes to get one of these recorded, but it’ll be good to know what does and doesn’t work if I end up doing future versions.
From the feedback I’ve gotten I think these video walk throughs are amongst the most useful learning tools for Web Connection and the Web Control framework and so I’m hoping that updating them to the latest version will have a few developers resisting the move to the 5.x Web Control framework taking a look at the framework and what it has to offer.
Enjoy.
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).
© Rick Strahl, West Wind Technologies, 2004 - 2010