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

Root-Relative Paths (~/path) in Web Connection

December 10, 2009 •

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 STRTRAN(Process.cUrlBasePath + SUBSTR(lcUrl,2),"//","/")


*  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




   THIS.oConfig = EVALUATE("THIS.oServer.oConfig.o" + this.Class)

   THIS.cUrlBasePath = THIS.oConfig.cVirtualPath




RETURN THIS.cUrlbasePath


*  wwProcess :: GetUrlBasePath


Assuming we want to retrieve the wwDemo process class configuration we’d access:




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):







Which maps to the class defined in wcdemomain.prg:



cHTMLPagePath = "d:\westwind\wconnect\"

cDATAPath = ".\wwDemo\"

cVirtualPath = "/wconnect/"




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)




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.

Posted in:

Feedback for this Weblog Entry

re: Root-Relative Paths (~/path) in Web Connection

Mike McDonald
December 11, 2009

It looks like the automatic post-processing has changed some of the sample URLs that should be "~/" to show up as "/wconnect/weblog/" in your text above.

re: Root-Relative Paths (~/path) in Web Connection

Rick Strahl
December 11, 2009

Thanks Mike. Kind of a problem when you willfully add those URLs into content, eh? Fixed by injecting spaces into those URLs.

re: Root-Relative Paths (~/path) in Web Connection

Phil Connolly
December 16, 2009

I don't think there is anything wrong with using a configuration as the basis for path translations. It should be easy enough to manage, especially since the value of the configuration is handled automatically by setup wizard code.

But for an alternative approach, I pasted a class I have been using for years which automates the translation of any virtual path to its physical path. It uses ADSI on its first request to pull virtual path info from your webserver for all websites, then subsequent requests use the cached data (so ADSI is only used on the first lookup).


re: Root-Relative Paths (~/path) in Web Connection

Rick Strahl
December 16, 2009

@Phil - Hmmm... started thinking about this a little a more after your comment. Getting the virtual actually is relatively straight forward since IIS provides the ADSI path which includes the name of the virtual.

How about something like this:


I think this should work. Virtuals are always one level off the root I think - as long as that's true this should work. Older IIS servers that don't include the key or Apache then still use the existing approach.

re: Root-Relative Paths (~/path) in Web Connection

Phil Connolly
December 16, 2009

I think I first wrote that class back before IIS gave us those nice server variables ?? Oh well, I still use it for some other purposes where I need to enumerate all the sites and virtuals of a web server....

In my experiences, APPL_MD_PATH returns the paths for the root, unless they are called from within a virtual, then it is the paths for the virtuals. Sites can also create virtuals deep in the site's folder tree:


and have the virtual point to any physical path. I don't know what is returned by the server variables for those cases. At any rate it probably doesn't matter for you, the APPL_MD_PATH variable should start with the root moniker no matter where in the virtual tree you request it from so you could extract the root and/or virtual out in all cases except perhaps deeply buried virtuals. Not sure why someone would want to do that... but there is always at least one.

re: Root-Relative Paths (~/path) in Web Connection

Rick Strahl
December 16, 2009

APPL_MD_PATH returns the virtual path. And although you can nest virtuals inside of other virtuals the virtual is always a single name. The path may be nested but the virtual isn't (or at least would be accessible through the short path).

So this appears it's doing exactly what we want - to return just the logical, root relative base path of the current request.


January 26, 2011

O, its great post! <a href="http://essay-writer.org/prices.php">essay sale</a>

© Rick Strahl, West Wind Technologies, 2003 - 2022