Recently Scott McCulloch found that when using URL rewriting technique in ASP.NET 1.x I discussed in URL Rewriting in ASP.NET, Image Web controls with the ~ in the ImageUrl property don't resolve correctly. Specifically, if you have an example of an actual page of, say, /RewriterTester/ListProductsByCategory.aspx?CategoryID=1 that has a “friendly URL” of /RewriterTester/Products/Beverages.aspx, and that page includes an Image Web control with an ImageUrl like ~/images/Logo.gif (meaning there's a file /RewriterTester/images/Logo.gif), then when visiting /RewriterTester/ListProductsByCategory.aspx?CategoryID=1 the image is displayed correctly, but visiting the “friendly URL” results in a broken image. (Whew, what a mouthful!) To further complicate things, if you use the ResolveUrl() method, it just works. That is, if on the page you display the output of ResolveUrl(”~/images/Logo.gif”), the correct URL is displayed, regardless if you visit the actual URL or the friendly URL.
After some serious head scratching and digging through the Base Class Library code with Reflector, I found the problem. When the Image Web control is rendered, its src attribute is formulated by calling the Control class's ResolveClientUrl() method. ResolveClientUrl() method is similar to the standard ResolveUrl() method (which works, remember), except for one important fact: it creates relative paths rather than absolute paths. So, ResolveUrl() gives us something like /RewriterTester/images/Logo.gif, but what ResolveClientUrl() gives us is images/Logo.gif. Since the path rewriting has occurred long before the Image Web control is rendered, the Image Web control - regardless of whether or not the person is visiting the friendly URL or not - thinks its path is /RewriterTester/ListProductsByCategory.aspx. So, when it resolves ~/images/Logo.gif, it says, “Ok, well ~/images/Logo.gif is really /RewriterTester/images/Logo.gif, but the relative path for that is just images/Logo.gif,” so it returns just this. This works great when visiting the actual URL, but when visiting the friendly URL, the browser gets the <img> tag with the src of images/Logo.gif. Since the browser has visited /RewriterTester/Products/Beverages.aspx, it requests the image at /RewriterTester/Products/images/Logo.gif. And that, in a nutshell, is the problem.
So what's the solution? Well, I can think of one simple solution: don't specify the ImageUrl for the Image tag in the declarative syntax. Rather, specify it in the Page_Load event handler like so:
if (!Page.IsPostBack)
ImageID.ImageUrl = Page.ResolveUrl(”~/images/Logo.gif”)
This will work since ResolveUrl() gets the absolute path to the file, not the relative path like ResolveClientUrl(). This answer, for me, lead into another question: why does ASP.NET need two methods for resolving URLs? That is, why have ResolveClientUrl() at all (which is internal, by the way, meaning you can't call it directly - only classes in same assembly can). Looking at the Callee Graph in Reflector, the following methods call ResolveClientUrl():
- HyperLink.AddAttributesToRender()
- HyperLink.RenderContents()
- Image.AddAttributesToRender()
- Panel.AddAttributesToRender()
- TableStyle.AddAttributesToRender()
- HtmlControl.PreProcessRelativeReferenceAttribute()
I guess I'm missing why a relative path is ever needed. Won't an absolute path always do the trick? And if relative paths might be needed, why make ResolveClientUrl() internal? Why not make it public like ResolveUrl()?