Root Path Support with ~ in Web Connection Response Output
May 12, 2012 •
Here's a little not so well known tip that's useful if you're using Web Connection: When output is rendered, the output automatically expand URLs that use the ~/path syntax, expanding any URLs found to fully application relative URLs on the page.
What it does is that you can write URLs like this (assuming a virtual directory of wconnect):
<a href="~/WebControls/Helloworld.wcsx">Hello World</a>
and expand that URL out to:
<a href="/wconnect/WebControls/Helloworld.wcsx">HelloWorld</a>
~/ paths basically say: Fix up the ~ to mean the base path of my Web Application. This allows you to create application rooted URLs consistently regardless of which virtual directory or root folder the URL is called from. This different than relative paths which depend on the current location of the page that is loaded in the browser - the path generated is a Web server root relative path that was generated by the server based on the active application's path.
This means if your app runs in a virtual directory called wconnect the root path resolves to /wconnect (root folder/wconnect subfolder). If you're running in the root of the site, the root path resolves to /. Why should this matter? If you're developing applications that target different environments the base path of the Application might change. For example, you might develop your application in a virtual directory for development, but deploy the live application into a root Web, or a virtual with different names. Using ~/ urls, no changes are required when the application is moved to the live server.
Web Connection 5.0 also introduced a code based equivalent in Process.ResolveUrl():
lcUrl = Process.ResolveUrl("~/WebControls/HelloWorld.wcsx")
which accomplishes the same thing for you in code so you can use this function for creating root relative paths from your code. This is especially useful for generic and reusable code which may not know where it is running from. The Web Control framework makes extensive use of ResolveUrl for any URL properties on which it always calls ResolveUrl(). ResolveUrl() ignores full path urls if passed and returns them as-is. Urls that contain ~/ in the path are translated.
Note that this feature started out as a feature specific to the the Web Control Framework, but it since then has migrated into the core framework and the wwPageResponse/wwResponseString objects to post-process in all output generated through the Response object. This means it works for Web Control Framework Pages, raw Response.Write() output, templates and scripts etc. It's only applied to HTML output when the Content Type is set to text/html.
Url Pathing Options
Note that this ~/ pathing is different than relative pathing using ../ or rooted pathing which starts with a / on the root site. Rather the ~ syntax creates an application relative URL that is always the same regardless of where your app runs.
Whenever possible when creating URLs in your applications both in markup and code you should choose paths in this order:
- Relative Paths
Use relative paths whenever possible because they're the most portable. Relative paths (../css/standard.css or css/standard.css) tend to be the most portable because as long as your resources are relatively stored to each other they will always work. Problems with this arise only if the resources are moved relative to each other which is probably rare. - Virtual Paths
Use application relative paths because they are portable per application. Virtual path specifiers (~/css/standard.css which expands to /wconnect/css/standard.css) are always rooted to the application's root folder. In Web Connection you specify this value in the server configuration via the cVirtualPath in wc.ini or web.config using the VirtualPath key. The great thing about ~/ paths are that there's no relative pathing involved if you don't know where your request might be running from. They are also super useful for Web Control Framework User Controls that might be dropped into pages running for many different subfolders. - Rooted Paths
Never use hardcoded paths to site or even rooted Webs. You should NEVER EVER use hard coded URL paths to reference anything in your local site. While this will work in whatever environment you're working in it'll break as soon as you move the site. At that point you have lots of links to clean up and that's never a good idea.
Both relative paths and virtual paths are adaptable and the big advantage of these are that they are portable. If you move the site or move to a different virtual folder you don't have to change anything (ok - you have to change the cVirtualPath config property, for ~/).
Relative paths are native to HTML and are parsed as the page is loaded. ~/ paths are expanded on the server and if you're using the wwPageResponse class as your Response class in Web Connection (which is the default in Web Connection 5.0) then any embedded ~/ paths are automatically expanded for you.
How it works and What is Transformed
The ~/ parsing is implemented as a post-processing feature of the wwPageResponse and wwResponseString classes. Basically Web Connection by default renders all of its output into a string first before potentially rendering the output into other outputs. For example, when running in file based mode Web Connection's output goes into a temporary file, but Web Connection (5.0) first renders the output to a string before explicitly writing the output to a file later. This switch occurred in Web Connection 5.0 to allow more control over the HTTP output created - using a string allowed for header manipulation because the final output doesn't get written to IIS until the response is complete. This means you can now add headers and cookies even when output has already been written to the Response.
In Web Connection 5.0 the wwPageResponse class handles all primary output, and the ~/ processing is hooked up in the Render method like this:
*** 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
The code first makes sure we're dealing with HTML content only and then looks to translates any embedded URL expressions in attributes (the first STRTRAN) and in CSS styles (the second url() based syntax).
Based on this Web Connection will expand:
<a href="~/webcontrols/helloworld.wcsx">Hello World</a>
to:
<a href="/wconnect/webcontrols/Helloworld.wwd">Hello World</a>
Notice thought that this will produce different results:
<a href="~/webcontrols/helloworld.wcsx">~/webcontrols/HelloWorld.wcsx</a>
which produces:
<a href="/wconnect/webcontrols/helloworld.wcsx">~/webcontrols/HelloWorld.wcsx</a>
Note that the second ~/ tag wasn't expanded because it's not defined inside of an attribute - the plain string just stays as designed. If you want that sort of thing to work you have to use <%= Process.ResolveUrl() %> instead:
<a href="~/webcontrols/helloworld.wcsx"><%= Process.ResolveUrl("~/webcontrols/HelloWorld.wcsx") %></a>
You can also expand paths in css tags like the following:
<style> .salesitem { padding: 10px; padding-right: 30px; background-image: url(/wconnect/css/images/pin.gif); } </style>
which expands out to:
<style> .salesitem { padding: 10px; padding-right: 30px; background-image: url(/wconnect/css/images/pin.gif); } </style>
It also works for inline styles:
<a style="background-image:url(/wconnect/css/images/help.gif)"
href="~/webcontrols/Helloworld.wwd">Hello World</a>
which transforms into:
<a style="background-image:url(/wconnect/css/images/help.gif)"
href="/wconnect/webcontrols/Helloworld.wwd">Hello World example</a>
Configuration
This feature is mostly transparent - there's nothing you have to set up or configure to make the render parse these values. There are two requirements though:
- Make sure you're using wwPageResponse or the wwResponseString class for Response rendering
- Make sure the VirtualPath property is set in web.config or wc.ini
wwPageResponse
wwPageResponse is Web Connection 5.0's default response class and unless you've explicitly overridden it this is the class used. If you're using an older version of Web Connection or you have an old application that was migrated to Web Connection 5.0 you might want to check and potentially switch to this class.
cResponseClass = "wwPageResponse" && wwPageResponse40 for 4.x compatibility
in your Process class property definitions header.
VirtualPath Configuration
Process.ResolvePath and Process.cBaseUrl retrieve the application's base path from the VirtualPath setting in the YourApplication.ini file. Specifically it comes out of the process class configuration section. For example, here's the wwDemo configuration section:
[Wwdemo] Datapath=C:\WWAPPS\WC3\wwDemo\ Htmlpagepath=c:\westwind\wconnect\ Virtualpath=/wconnect/
The VirtualPath key specifies the virtual path that is used. Note that this path is used for various other things as well so you should always set this in your applications. Web Connection's configuration Wizard automatically sets this but if you manually copy applications always make sure to set these values explicitly.
Summary
If you haven't used this feature before - take advantage of it. As small as this feature is, it's very useful and greatly simplifies creation of cleaner and consistent URLs in your application. The post parsing mechanism is totally transparent - you don't have to do anything to make this work. No changes are required other than making sure that the application's INI file is set.