Web Connection Tip: Virtual Path Resolving with ~/
March 31, 2013 •
Getting paths to resources and media content right in Web applications is a crucial part of Web development. In general you never ever want to hard code a path to an internal site link or resource within your application. Whenever possible any page code that creates HREF links or links to CSS, Images or scripts should either be relative (ie. ../image.png) or relative to the root site.
Relative linking is native to HTML and CSS and if possible you should use it. Relative paths are relative to the currently active URL and so allow you to always keep relative locations in sync. If the entire application moves to a new folder the links still work because they're relative to each other.
Relative paths can break down when you move a page to a different location within a site. If you have a page that has relative links to an image in a subfolder (like images/refresh.png) and you then move this page up one level in the folder hierarchy it no longer finds the image in an images subfolder so either the link needs to be adjusted or your link breaks.
Relative paths are good, but you can't always use them - specifically from generic code that doesn't live on a page per se or that code that needs to run from different pages. Generally this happens in custom components or helper functions. Examples of these inside of Web Connection itself are Web Control Framework pages or helper functions like the wwHtmlHelper functions.
Enter Virtual Path Syntax ~/
Web Connection - much like ASP.NET - supports virtual path syntax which is as follows:
- ~/page.wwd
- ~/images/home.png
- ~/admin/monitor/monitor.htm
This syntax provides virtual directory path relative links that are in effect application specific. Give a virtual directory of /wconnect the above paths evaluate to:
- /wconnect/page.wwd
- /wconnect/images/home.png
- /wconnect/admin/monitor/monitor.htm
Where does it work?
You can use ~/ paths anywhere in Web Connection output when using the wwPageResponse class (which is the default for Web Connection 5.0 and later). When using this class all output that includes ~/ based paths are automatically fixed up. So regardless of whether you're using Response.Write() and ExpandTemplate(), ExpandScript() or a Web Control Framework page links are fixed up.
The following automatically convert to a full virtual path:
- <a href="~/admin/admin.aspx">Admin Page</a>
- <img src="~/css/images/home.png" />
- <a href="~/">Home</a>
- <div style="background-image: url(/wconnect/images/closebox.png)"></div>
So the first link would render as /wconnect/admin/admin.aspx etc.
How does it work?
Web Connection does this as part of the Response class Rendering process where it checks for text/html content and if it is searches for ~/ strings to replace. Specifically it looks for ="~/ and url(/wconnect/ in the output and replaces it. Internally Web Connection uses Process.cBaseUrl which provides the base virtual url for a virtual directory or root site. The value is filled from the YourApp.ini file and the VirtualPath property for your Process class:
[Wwdemo]
Datapath=C:\WWAPPS\WC3\wwDemo\
Htmlpagepath=c:\westwind\wconnect\
Virtualpath=/wconnect/
This value is read on application startup and then assigned to the Process.cUrlBasePath and is available anywhere. When wwPageResponse renders it does the replacement like so:
FUNCTION Render() LOCAL lcOutput, lcBasePath *** Fix up ~/ paths with UrlBasePath IF THIS.contentType = "text/html" AND VARTYPE(Process) = "O" lcBasePath = Process.cUrlBasePath IF !EMPTY(lcBasePath) this.cOutput = STRTRAN(this.cOutput,[="~/],[="] + lcBasePath) this.cOutput = STRTRAN(this.cOutput,[url(/wconnect/],[url(] + lcBasePath) ENDIF ENDIF
…
Resolving Virtual Path Syntax Programmatically
In your own code if you need to parse a virtual path you can use the same virtual path syntax with Process.ResolveUrl() method. As long as you are running with a Web Connection Process instance the top level PRIVATE Process object is in scope and you can call ResolveUrl() on it to resolve virtual relative paths.
So from within your FoxPro code you can easily do:
lcjQuery = Process.ResolveUrl("~/scripts/jquery.min.js")
to get /wconnect/scripts/jquery.min.js.
Paths are important!
This seems like a very simple thing and you might even write this off as, "ah I don't need this I just use relative paths". But I'm often surprised how often I see code that doesn't take this into account and ends up hardcoding virtual paths like /wconnect/css/images/home.png. You should NEVER write code like that either in HTML or in FoxPro codebehind. If in the future your app moves to a new virtual directory or - more commonly - a root site links like that simply will no longer work and break your code.
Remember to always use relative paths - either page relative or virtual relative - to ensure that your site remains portable. It's an important lesson to remember if you've had to go in and fix up links once after the fact.