URL Rewriting with ASP.NET and Handling Images and CSS
As usually, ScottGu has an excellent post about URL rewriting with ASP.NET. No need for me to repeat what he already said 🙂
However at the end of his post, Scott has a section for dealing with CSS and Image references correctly, which has been causing problems for some including myself. The problem is with the use of the tilde (~) notation for ASP.NET controls and for the CSS you must use the absolute path meaning “/style.css” instead of just “styles.css”.
Handling CSS
Of course you can simply put an absolute path and your done. The solution I’m proposing here is an alternative and simulates the ~ notation. This solution was inspired by what Scott did with the forms control adapter.
Looking at typical page (including master pages) we have the following:
1 <head runat="server"> 2 <title>My Page</title> 3 <link href="Stylesheet.css" rel="stylesheet" type="text/css" /> 4 </head>
So that got me thinking that since the head is now a server it must also follow the ASP.NET rendering methods. So based on Scott’s sample you can add the following to the Form.browser file:
1 <browsers> 2 <browser refID="Default"> 3 <controlAdapters> 4 <adapter controlType="System.Web.UI.HtmlControls.HtmlForm" 5 adapterType="FormRewriterControlAdapter" /> 6 <adapter controlType="System.Web.UI.HtmlControls.HtmlLink" 7 adapterType="LinkRewriterControlAdapter" /> 8 </controlAdapters> 9 </browser> 10 </browsers>
Essentially we are going to override the rendering for the HtmlLink control. The C# code to do that (LinkRewriterControlAdapter) is:
1 public class LinkRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter 2 { 3 protected override void Render(HtmlTextWriter writer) 4 { 5 base.Render(new RewriteLinkHtmlTextWriter(writer)); 6 } 7 8 } 9 10 public class RewriteLinkHtmlTextWriter : HtmlTextWriter 11 { 12 #region Constructors 13 14 public RewriteLinkHtmlTextWriter(HtmlTextWriter writer) : base(writer) 15 { 16 this.InnerWriter = writer.InnerWriter; 17 } 18 19 public RewriteLinkHtmlTextWriter(System.IO.TextWriter writer) : base(writer) 20 { 21 this.InnerWriter = writer; 22 } 23 24 #endregion 25 26 public override void WriteAttribute(string name, string value, bool fEncode) 27 { 28 if (name == "href") 29 { 30 HttpContext Context = HttpContext.Current; 31 if (Context.Items["ActionAlreadyWritten"] == null) 32 { 33 string sStylesheetName = value; 34 int iSlashPos = value.LastIndexOf('/'); 35 if (iSlashPos >= 0) sStylesheetName = value.Substring(iSlashPos); 36 37 value = GetBaseURL() + "/" + sStylesheetName; 38 39 Context.Items["ActionAlreadyWritten"] = true; 40 } 41 } 42 base.WriteAttribute(name, value, fEncode); 43 } 44 }
This will override the rendering of the href attribute value for all <link> tags. The value coming in has already been resolved by ASP.NET, that is why there is some parsing for slash (/).
Handling Images
The tilde notation (~) works great if you are only doing one level of URL rewriting. If you want to do URL rewriting with mulitple levels ex: http://mysite.com/products/category/subcategory/subsubcategory/etc.., then (~) notation will no longer work since ASP.NET will resolve the URL relative to real ASPX page. For this case I find it simplest to use HTML elements and specify the href with inline code. Ex:
1 <a href="<% =GetBaseURL() %>/">Home</a>
Hope this helps. Please feel free to comment (go easy this is my first post :))
Gabriel said,
May 16, 2007 at 9:49 pm
Hi there, I’m thinking this article is going to help me… but at this point I haven’t implemented the forms.browser file. What is that all about, can I create it just for this purpose?
Thanks!
pstatho said,
May 17, 2007 at 11:28 am
The Forms.browser file is very useful for tweaking the HTML that gets outputted from ASP.NET.
In Scott’s example he used to change the ACTION attribute of the tag from ACTION=”mypage.aspx?param1=value…” to the URL rewritten value ACTION=”\products\CDs”.
Inspired from that I used a similar technique to modify the output of the tag for CSS so that I can simulate the tilde (~) functionality i.e. reference the virtual root.
Hope this helps,
Perry
Kyle Banashek said,
May 30, 2007 at 12:42 am
Where is the GetBaseURL() method code?
Rupert said,
July 13, 2007 at 1:35 pm
Hi Perry, this is great stuff! Just a couple of things that I ran into which might help others:
Your solution works fine with one css file, but if you have more than one, only the first gets rendered because of the
if (Context.Items[“ActionAlreadyWritten”] == null)
line. Also this is using the same key as the formRewriter which could cause problems.
I made a change to generate the name of the Context.Items key from the value of the href and it works fine now. Here is the code:
public override void WriteAttribute(string name, string value, bool fEncode)
{
if (name == “href”)
{
HttpContext Context = HttpContext.Current;
string contextItemKey = value + “HrefAlreadyWritten”;
if (Context.Items[contextItemKey] == null)
{
string sStylesheetName = value;
//I’m getting the absolute url slightly differently
//I imagine this is what you did in your GetBaseUrl() method
int iSlashPos = value.LastIndexOf(“../”) + 3;
if (iSlashPos >= 0) sStylesheetName = value.Substring(iSlashPos);
value = VirtualPathUtility.ToAbsolute(“~/” + sStylesheetName);
Context.Items[contextItemKey] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
Finally The GetBaseUrl() method doesn’t exist in any of the code you’ve posted, so I’ve implemented something similar.
Thanks again for your post it was really helpful!
Rupert
Rupert said,
July 13, 2007 at 1:39 pm
Oops, slight problem with that last post, I actually meant:
public override void WriteAttribute(string name, string value, bool fEncode)
{
if (name == “href”)
{
HttpContext Context = HttpContext.Current;
string contextItemKey = value + “HrefAlreadyWritten”;
if (Context.Items[contextItemKey] == null)
{
string sStylesheetName = value;
//I’m getting the absolute url slightly differently
//I imagine this is what you did in your GetBaseUrl() method
int iSlashPos = value.LastIndexOf(“../”);
if (iSlashPos >= 0) sStylesheetName = value.Substring(iSlashPos + 3);
value = VirtualPathUtility.ToAbsolute(“~/” + sStylesheetName);
Context.Items[contextItemKey] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
Cheers!
pstatho said,
July 14, 2007 at 12:27 pm
Rupert,
You are absolutely right! I did not anticipate multiple CSS files. You’re solution with the contextItemKey is exactly what I would have done as well.
My GetBaseURL(), was just an example of something that could be done. In my case I am actually loading the BaseURL from a config file.
Perry
Jay said,
December 28, 2007 at 12:34 am
You could also do something like this – <link rel=”stylesheet” href=”” type=”text/css” />
Jay said,
December 28, 2007 at 12:35 am
oops I meant – <link rel=”stylesheet” href=” ” type=”text/css” />
Manir said,
September 19, 2008 at 7:05 pm
Hi Perry,
I am trying to implement Approach 1 from Scott Gu’s blog (http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx) Which is I am adding extra string to the url to perform SEO. For example insted of using http://mysite/mypage.aspx?id=10 I am using http://mysite/mypage.aspx/id_10-general-books. In my site I am also using ASP.NET themes, Skins… When there is strings like “/id_10-general-books” – it cannot resolve the image paths in one level down in the directory structue like http://mysite/user/mypage.aspx/id_10-general-books — If I take out “/id_10-general-books” – it works fine. Please help me how to resolve these issues?
I appreciate your response.
Thanks,
Manir
Rama said,
October 17, 2008 at 10:38 am
Where is the GetBaseURL() method code?
kenneth das said,
November 24, 2008 at 7:20 am
i found this article very useful………i would like to add how to find out base url ie GetbaseURL……
public static string GetBaseURL()
{
string url =HttpContext.Current.Request.Url.Scheme + “://” + HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.ApplicationPath.TrimEnd(‘/’) + ‘/’;
//EPiServer’s url start with a / so remove the url if (when) it contains one
if(url.EndsWith(“/”))
return url.Remove(url.LastIndexOf(“/”));
else
return url;
}
Jinesh said,
January 8, 2010 at 12:02 pm
i hv read “http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx” article…nd i hv done url rewrite…bt i m getting problem of css..
i hv read this article also…nd i m new in .net
i dont know where to write GetBaseURL() function ?
can you please explain me actully what to do to apply css and images.
I am using master page and also Usercontrol. and i have also backoffice system which comes under “root/backoffice/login.aspx”
Looking for reply.
Thanx
pstatho said,
January 8, 2010 at 1:30 pm
You can put the function in the Global.asax.cs file and then call it as Global.GetBaseURL()
mani said,
May 11, 2009 at 12:52 pm
hi , i have problem on using CSS and images i have one master pages that has been used it for other pages..
i have used the images like ./images/logo.jpg this working on ,when i run it on master page as a start page but if i click any other link the image and css has not working.
Why ??? how to solve this …???
pstatho said,
May 18, 2009 at 1:08 pm
@mani
It looks like you problem is related to the fact you have image sources that start with “./images”. This tells the browser to look for a sub-folder in the current folder location. So naturally this work for pages in root, but not for any pages in a what looks like a sub-folder.
If you do not need you application to be in a virtual directory then you can simple do “/images/…”.
If you need to support virtual directories, then you need to come up with a way to programatically determine the base URL, hence why I created function called GetBaseURL(). This could either be config or using the VirtualPathUtility
http://msdn.microsoft.com/en-us/library/system.web.virtualpathutility.aspx
Hans said,
October 27, 2009 at 5:23 am
Folks
The problem with using themes in ASP.NET apps and using URL rewriting can also be solved by adding a rule to the web.config file. I solved my problem like this.
This will resolve your css and image linking issues.
tks H
Jinesh said,
January 26, 2010 at 10:57 am
Hay pstatho
thanx for reply…i have include all css but still i didnt get images ..i dont want to include all path like “vitualdirectory/images/a.jpg”. i want to include like “images/a.jpg”. i
So how to control images just like css ?
Thanx for reply once again.
Jinesh said,
January 26, 2010 at 11:09 am
Also when i am using AJAX Control toolkit 2.0 than i am getting
error like
‘~//mympa/WebResource.axd?d=Jx6zFgfbeO7rXdGiLSLbzZCK4cpFhcJS3JjWlCtxm0b4q9uVfWnzeCSu7SjmfoXuLI8SS9W4-jdF69BQKn6eNg2&t=633976124039843750’ is not a valid virtual path
If you have any kind of solution where project includes in dot net
Master Page,
UserControl,
Multiple css from css folder,
Javascript from js folder,
AJAX Control Toolkit,
Images from image folder,
admin folder for backoffice which not require seo url.
Any reply is appreciated,
Thanx a lot.
Davidov said,
July 1, 2010 at 9:12 pm
Hello, i am trying this solution, but without any success.
<a href="/”>Home
I got an error “The name ‘Global’ does not exist in the current context”.
Davidov said,
July 1, 2010 at 9:16 pm
is the function
Davidov said,
July 1, 2010 at 9:17 pm
% =GetBaseURL() %
I am sorry about the spam
campster said,
November 2, 2010 at 9:22 am
Davidov, what “global” is? a control?
http://coalition.media-spine.com/members/williamal said,
April 17, 2013 at 6:10 pm
hello there and thank you for your information – I have definitely picked up something new
from right here. I did however expertise some technical issues using this web site, since I experienced to reload the website
lots of times previous to I could get it to load properly.
I had been wondering if your web hosting is OK?
Not that I’m complaining, but slow loading instances times will often affect your placement in google and could damage your high-quality score if advertising and marketing with Adwords. Well I’m adding this RSS to my e-mail and can look out for a lot more of your respective exciting content.
Ensure that you update this again very soon.