Why does Url Rewriting break all my image links?
Aug
22
Written by:
Friday, August 22, 2008 2:12 PM
If you've started using Url Rewriting in your project, or have changed to using a different Url Rewriting scheme, something that
often comes up is 'why are my image/css/js' links no longer working?
The Symptom
This has been brought to my attention as an issue through people using the Url Master software, and finding it isn't compatible with a particular third-party module.
It doesn't take long to figure out what the problem is : it normally shows up as a broken image link, or CSS file missing. The reason why is this : the Url Master rewrites Urls by using the
ASP.NET context.RewriteUrl(url, rebaseClientPath) call. For this call, it was using the 'true' value for rebaseClientPath.
Starting with the next release of the Url Master software, there wil be an advanced rewriting option to set the value of the 'rebaseClientPath' call to either true or false in the Friendly Url Settings.
Now, if you're not a developer that's not going to mean much, so I'll state it another way : rebaseClientPath=true means
that all relative Urls should be based from the current, virtual Url. Still clear as mud? How about a small piece of Url theory.
What is Url Rewriting Anyway?
Url Rewriting is the process of taking a Url as requested by a site visitor, and rewriting it into another form. In the case of the DotNetNuke framework, it means taking the Url
for a DNN page, and rewriting it from a format that people can understand (domain/page-name.aspx) into a format that the DNN framework can understand domain/default.aspx?tabid=53)
When you rewrite Urls, the site visitor doesn't see any change to the url - their browser still shows /page-name.aspx. It's only the rest of the DNN framework that see the rewritten value.
Still with me? Ok, the original, Human Friendly Url is called the Virtual Url, and the rewritten Url is, well, the Rewritten Url. It's called the Virtual Url because
it doesn't point to anything real on the server - it's a made-up value, or virtual value, designed only to make it easier for people to look up pages. Think of it as a programmatic phone directory - you look up the persons' name, then use the number. In the same way, you ask the site for a page name that makes sense, it works out what you are looking for.
Base to Rebasing
Now we understand what virtual Urls and rewritten Urls are, we can go back to the 'rebaseClientPath' discussion. Remember, when 'rebaseClientPath' is true for rewriting, any request for a relative Url will be rebased from the virtual Url, when you use the 'ResolveClientUrl' call to obtain a Url in your code. A relative Url is one that is referenced from somewhere other than an absolute Url. For instance, an absolute Url would be http://www.ifinity.com.au/images/green-ok.gif. A relative Url would be ~/images/green-ok.gif.
With a rebased client path, ASP.NET will transform that relative Url based on the path of the virtual Url.
This means on a virtual url of domain/page-name.aspx, a client path of ~/images/green-ok.gif will be transformed into domain/images/green-ok.gif. With a virtual path of domain/page-name/child-page-name.aspx, a client path of ~/images/green-ok.gif will be transformed into domain/page-name/images/green-ok.gif. The difference is that the 'page-name' value is inserted into the path - and this will normally result in a broken link to an image, css file or js file.
With a rebaseClientPath=false, all relative urls are from the site root, so no matter what the virtual Url, you'll always end up with domain/images/green-ok.gif.
An Experiment with Relative ASP.NET Paths
From the point of working out what effect rebaseClientPath has on Url Rewriting, it just gets more complicated. There are many ways in which you can reference a relative path in ASP.NET - some of which are affected by rewritten Urls, some of which aren't. Then you've got the effect of the relative physical path of images. So I decided to put together an experiment
The Test Control
I created a new ASCX file, called 'text.ascx'. The entire code for it is below:
<%@ Control Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Web" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Image5.ImageUrl = "~/images/green-ok.gif";
Image6.ImageUrl = "~/images/green-ok.gif";
Image7.ImageUrl = ResolveUrl("~/images/green-ok.gif");
Image8.ImageUrl = ResolveClientUrl("~/images/green-ok.gif");
label2.Text = "<img src='" + ResolveClientUrl("~/images/green-ok.gif") + "'/>";
}
</script>
<h1>Root/Images directory</h1>
<h2>img control, full path with root specifier</h2>
<div>~images/green-ok.gif<img src='~/images/green-ok.gif'/></div>
<div>~images/green-ok.gif (server)<img id="Img3" src='~/images/green-ok.gif' runat="server" /></div>
<div>ResolveUrl("~/images/green-ok.gif")<img src='<%= ResolveUrl("~/images/green-ok.gif") %>' /></div>
<div>ResolveClientUrl("~/images/green-ok.gif")<img src='<%= ResolveClientUrl("~/images/green-ok.gif") %>' /></div>
<h2>asp image control, full path</h2>
<div>(asp:image) ~/images/green-ok.gif<asp:Image ID="Image5" runat="server"/></div>
<div>(asp:image) ~/images/green-ok.gif (server)<asp:Image id="Image6" runat="server"/></div>
<div>(asp:image) ResolveUrl("~/images/green-ok.gif")<asp:Image ID="Image7" runat="server"/></div>
<div>(asp:image) ResolveClientUrl("~/images/green-ok.gif")<asp:Image ID="Image8" runat="server"/></div>
<div>(asp:label with img html) ResolveClientUrl("~/images/green-ok.gif")<asp:label ID="label2" runat="server"/></div>
<h2>img control, full path</h2>
<div>images/green-ok.gif<img src='images/green-ok.gif'/></div>
<div>images/green-ok.gif (server)<img id="Img4" src='images/green-ok.gif' runat="server" /></div>
<div>ResolveUrl("images/green-ok.gif")<img src='<%= ResolveUrl("images/green-ok.gif") %>' /></div>
<div>ResolveClientUrl("images/green-ok.gif")<img src='<%= ResolveClientUrl("images/green-ok.gif") %>'
/></div>
I placed this ascx control into a directory called /controls/testcontrol and named the file 'test.ascx'. I then embedded this file
into a page that was using Url Rewriting. The test file simply shows an example of each of the many different ways you can reference an image file with a relative path.
It uses a mix of plain-html img tags, with and without the runat='server' tag, plus asp:image tags, and also Html formed in code and written out (the label tag). Some of these calls
use the ResolveUrl call, others use the ResolveClientUrl call.
The test is designed to display which of the methods is the most robust to use with Url Rewriting. This is especially true if you're building components for use in other
websites, because you won't have control over what sort of Url scheme is used on that website, so it's better to make your code flexible.
First Test
The first test I did was request the page using a path of domain/virtualdir/page-name.aspx, where virtualdir is an IIS Virtual Directory, and page-name.aspx is a virtual Url, which will be rewritten to default.aspx?tabid=xx. This is because I was testing on a development machine which has several virtual directories for this purpose.
There should be no difference between testing from an IIS virtual directory, and the root IIS website. I mention it for completeness.
This test was done with the underlying Url Rewriting set to use 'rebaseClientPath=true'.
Results
You can see the results in the picture below. Where the Url resolved correctly, it shows the green-ok gif, where it didn't, it shows a broken link.
The broken image links, in order from top to bottom, requested Urls that looked like this:
- 1. http://domain/virtualDir/~/images/green-ok.gif
- 8. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 11. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 12. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 13. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
As you can see, the first one is a malformed Url, and the following four are relative to the file path of the control, rather than relative to the virtual path of the requesting Url. This is because the last 4 broken links all use a relative path without the '~' to start with, which tells ASP.NET to get the relative file path, rather than the path relative to the request. Note that these last 4 images with broken links are all either runat='server' or inline .NET calls to resolve the path.
Second Test
The second test is the same control, same 'rebaseClientPath=true' as the first test, however, the virtual Url of the request is domain/page-name/child-page-name.aspx.
Results
Again, the broken image links, in order from top to bottom, requested Urls that looked like this:
- 1. http://domain/virtualDir/page-name/~/images/green-ok.gif
- 4. http://domain/virtualDir/page-name/images/green-ok.gif
- 8. http://domain/virtualDir/page-name/controls/testcontrol/images/green-ok.gif
- 9. http://domain/virtualDir/page-name/images/green-ok.gif
- 10. http://domain/virtualDir/page-name/images/green-ok.gif
- 11. http://domain/virtualDir/page-name/controls/testcontrol/images/green-ok.gif
- 12. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 13. http://domain/virtualDir/page-name/controls/testcontrol/images/green-ok.gif
The first thing to note here is that there are more broken links, yet the code was exactly the same. The only thing that changed was the
virtual Url that the page was requested on. You can see this caused problems becase the relative Urls were rebased from the path level of the Virtual Url. So, instead of the ~/images path being resolved to domain/virtualdir/images, it is resolved to domain/virtualdir/page-name/images. And because 'page-name' is just part of a virtual path,
there's no Images directory there, so you get a broken link. This is directly caused by the 'rebaseClientPath=true' in the rewriting scheme.
If you look closely, you'll see that the requests using 'ResolveClientUrl' are the ones that are affected, as it is this .NET call that makes use of the 'rebaseClientPath' boolean value. The 'ResolveUrl' based calls are still working, because they always work from the site root, rather than the requested path.
The other broken links are again from trying to resolve the physical path of the test.ascx control, but this time against the relative virtual path - about as incorrect as you can get.
Third Test
This time, I repeated the 2nd test, but changed the underlying Url Rewriting call to have 'rebaseClientUrls=false'. The request was still domain/page-name/child-page-name.aspx. There was no need to repeat test 1 with rebaseClientPath=false, because the rewritten path and the virtual path have the same relative 'path level'. By path level, I basically mean the number of '/' separators in the Url.
Results
And the broken image links, in order from top to bottom, requested Urls that looked like this:
- 1. http://domain/virtualDir/child-page-name/~/images/green-ok.gif
- 8. http://domain/virtualDir/controls/images/green-ok.gif
- 10. http://domain/virtualDir/child-page-name/images/green-ok.gif
- 11. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 12. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
- 13. http://domain/virtualDir/controls/testcontrol/images/green-ok.gif
The most important thing to note is that the calls using 'ResolveClientUrl' are the ones working again, and the list is almost the same as the results from the first test.
Conclusions
- Using the style of path/filename.ext in a plain html tag with no runat=server is a bad idea. This ignores the rebaseClientPath value of the Rewriting scheme and any rewriting will probably break the link.
- If you want to reference a file based on the physical file location of an ascx control or aspx page, then you should probably use the full path of the image from the root of the website, and mark the img tag as runat='server'. Number '2' and '3' always worked, as did '7'.
- Using ResolveClientUrl works OK, but is dependent on the use of the 'rebaseClientPath' value in the Rewriting call. If you don't have direct control over this, it's better to opt for the 'ResolveUrl' call.
- Using ResolveUrl over ResolveClientUrl means your code will work independent of the Url Rewriting scheme.
My recommendation, then, is to use the 'ResolveUrl' call in your code. And if you're having trouble with a control that you don't have the source code for, and you can see the img tag in the ascx file, then adding 'runat="server"' to that tag will probably solve your problem.
If you're having trouble with this, experiment with your Url Master installation by switching the 'rebaseClientPath' call to true/false, and see if that helps. You'll also want to check the rest of your site to ensure that it is working still after you make the change.
If you found a mistake or have some more to add, please do so via the comments below.
3 comment(s) so far...
Re: Why does Url Rewriting break all my image links?
So what are we to do for modules that we can't change? This issue causes broken images for the ActiveSocial application by ActiveModules (formerly Active Profile and Active Messaging applications). The recommendation by way of their forums is to change our ignoreRegex from this: (? to this: (? I decided to remove my installation of URL Master completely, and upon reinstall, I noticed that the ignoreRegex already looks like that! Is that a change you've made between versions, or some config on my machine left over after the removal and uninstall? Either way... I'm still stuck without images in ActiveSocial. Getting paths like this: domain.com/community/groups/groups-view/asg/5/Portals/15/activesocial/groups/_icons/gp_5_1.png
By Jonathan Puddle on
Thursday, March 19, 2009 11:44 PM
|
Re: Why does Url Rewriting break all my image links?
hmm, that's unfortunate. The comment box stripped out all my code. I'm basically talking about .pdf vs .pdf$
By Jonathan Puddle on
Thursday, March 19, 2009 11:45 PM
|
Re: Why does Url Rewriting break all my image links?
Excellent, just had to add rebaseClientPath=false in my httpmodule and everything works fine now :)
I've yet to check whether there are going to be any implications on postback though.
By Av on
Wednesday, July 07, 2010 2:08 AM
|