Click here to Skip to main content
Click here to Skip to main content

ResolveUrl in ASP.NET - The Perfect Solution

By , 26 Jan 2010
 

Introduction/Background

From my personal experience using ASP.NET, and from searching the web, I have found that the ResolveUrl method of the Page control (and basically, Control) presents us with some serious problems.

The most common one is that you just cannot use it outside of the page or control context.

Other problems are just bugs. It will not correctly handle some of the URLs you'll give it. For example, try Page.ResolveUrl("~/test.aspx?param=http://www.test.com"). The result is the very same input string... It will just do nothing. By looking into the ASP.NET code using Reflector, I found that all mechanisms that are supposed to convert the relative URLs to absolute URLs will search first for a "://" inside the string, and will return if found. So a query string is OK, unless you pass in a parameter with ://. Yes, I know that the query string parameter should be UrlEncoded, but if it isn't, it is still an acceptable URL. Seriously, check your browsers!

Other suggested methods on the web involve using VirtualPathUtility.ToAbsolute, which is pretty nice and handy, unless you pass in a query string with the URL... Because it will just throw an exception. It will also throw an exception for an absolute URL!

So I decided to find the ultimate solution.

Using the Code

First, I searched for the perfect variable that will give me the Application Virtual Path at runtime without a page context.

I found this to be HttpRuntime.AppDomainAppVirtualPath. It will work anywhere - even inside a timer callback! It gives the path without a trailing slash (ASP.NET makes a special effort to remove the trailing slash...), but that is OK, we can fix it :-)

Then, I did some tests on the original ResolveUrl code, and found where I need to replace what with the AppVirtualPath:

  1. When the URL begins with a slash (either / or \), it will not touch it!
  2. When the URL begins with ~/, it will replace it with the AppVirtualPath.
  3. When the URL is an absolute URL, it will not touch it. (ResolveUrl has a bug with this, as I said before...)
  4. In any other case (even beginning with ~, but not slash), it will append the URL to the AppVirtualPath.
  5. Whenever it modifies the URL, it also fixes up the slashes. Removes double slashes and replaces \ with /.

So I replicated all of that, but without the bugs. And here's the code:

public static string ResolveUrl(string relativeUrl)
{
    if (relativeUrl == null) throw new ArgumentNullException("relativeUrl");
 
    if (relativeUrl.Length == 0 || relativeUrl[0] == '/' || relativeUrl[0] == '\\') 
        return relativeUrl;
 
    int idxOfScheme = relativeUrl.IndexOf(@"://", StringComparison.Ordinal);
    if (idxOfScheme != -1)
    {
        int idxOfQM = relativeUrl.IndexOf('?');
        if (idxOfQM == -1 || idxOfQM > idxOfScheme) return relativeUrl;
    }
 
    StringBuilder sbUrl = new StringBuilder();
    sbUrl.Append(HttpRuntime.AppDomainAppVirtualPath);
    if (sbUrl.Length == 0 || sbUrl[sbUrl.Length - 1] != '/') sbUrl.Append('/');
 
    // found question mark already? query string, do not touch!
    bool foundQM = false;
    bool foundSlash; // the latest char was a slash?
    if (relativeUrl.Length > 1
        && relativeUrl[0] == '~'
        && (relativeUrl[1] == '/' || relativeUrl[1] == '\\'))
    {
        relativeUrl = relativeUrl.Substring(2);
        foundSlash = true;
    }
    else foundSlash = false;
    foreach (char c in relativeUrl)
    {
        if (!foundQM)
        {
            if (c == '?') foundQM = true;
            else
            {
                if (c == '/' || c == '\\')
                {
                    if (foundSlash) continue;
                    else
                    {
                        sbUrl.Append('/');
                        foundSlash = true;
                        continue;
                    }
                }
                else if (foundSlash) foundSlash = false;
            }
        }
        sbUrl.Append(c);
    }
 
    return sbUrl.ToString();
}

Points of Interest

After completing the code and testing over and over again and comparing to the original ResolveUrl, I started to test for performance... In most cases, my code executed faster than the original ResolveUrl by 2.7 times! I also tested inside loops that executed the code 100000s of times on different kinds of URLs.

History

This is the first version, and hopefully the last!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Daniel Cohen Gindi
Software Developer (Senior)
Israel Israel
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSystem.Web.VirtualPathUtility?membermwdiablo11 Oct '12 - 15:51 
Why would you not use System.Web.VirtualPathUtility?
AnswerRe: System.Web.VirtualPathUtility?memberDaniel Cohen Gindi11 Oct '12 - 20:31 
System.Web.VirtualPathUtility is for Paths, not Urls.
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

GeneralJust what I came here for...memberSixOfTheClock4 Aug '12 - 10:56 
I was looking for a solution to my ResolveUrl problem and here it is, taking me all of 5 minutes to implement. Excellent article! And here's a 5!
A programming language is to a programmer what a fine hat is to one who is fond of fancy garden parties. Just don't try wearing any .NET language on your head. Some of them are sharp.

GeneralRe: Just what I came here for...memberDaniel Cohen Gindi4 Aug '12 - 10:57 
Glad I could help! Smile | :)
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

SuggestionA suggestion to make it even bettermemberRobbie Couret9 Nov '11 - 9:55 
It does not work like Page.ResolveUrl would when the current page is in a subdirectory and you do not supply the subdirectory name in the call, for instance: lets assume we are currently on http://root/sub/page1.aspx and we then call:
string url = Page.ResolveUrl("page2.aspx");
then the url output would be "/sub/page2.aspx", however:
string url = WebUtil.ResolveUrl("page2.aspx"); // WebUtil being a static class containing this article's method
would yield "/page2.aspx" which is understandable since it can not make the subdirectory determination from the string parameter alone.
 
One could argue that the developer should remember to include the subdirectory in the relativeUrl parameter when making the call, but I think for increased backwards compatibility with Page.ResolveUrl and peace of mind, that the method should at least try to figure out what the current subdirectory is in these cases when HttpContext.Current (and HttpContext.Current.Request) is available.
Robbie Couret
Senior Software Engineer
Big Easy Software, LLC

GeneralMy vote of 5memberAnurag Gandhi4 Aug '11 - 20:46 
Nice approach. Well done.
GeneralGreat job!memberDotNetWise8 Dec '10 - 12:23 
Dude, you rocked my day! Laugh | :laugh:
GeneralMy vote of 5memberToeStumper1 Sep '10 - 6:02 
Great tool to handle a specific set of issues. I'll ding you for the title "Perfect...?" but the article and code were great!
GeneralRe: My vote of 5memberDaniel Cohen Gindi1 Sep '10 - 10:02 
Thanks! Always happy to help!
 
What do you know maybe Microsoft will adopt my code in the next .Net version Wink | ;-)
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

GeneralGreat job!memberToeStumper1 Sep '10 - 5:59 
I stumbled on to this when I was changing a Response.Redirect to Server.Transfer call. There was no need for my code to take a trip to the browser to get to the page I wanted so I went from this
 
string req = ResolveUrl("~/MyPage.aspx");
Response.Redirect(req);
 
to this
 
string req = ResolveUrl("~/MyPage.aspx");
Server.Transfer(req);
 
and found out it didn't work too well (Boom!). You gave a great explanation of the situation and provided a well done tool to address some concerns.
 
Thank you.
GeneralRe: Great job!memberDaniel Cohen Gindi1 Sep '10 - 9:56 
It's always nice to know I helped another good person Smile | :)
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

GeneralMy vote of 5memberAdam Maras26 Jan '10 - 10:54 
Take it from someone who eats, sleeps, and breathes ASP.NET; this is a wonderful solution, and I am most likely going to stick this in a helper class I use in virtually all of my projects. Thank you!
 
Adam Maras | Software Developer
Microsoft Certified Professional Developer

GeneralRe: My vote of 5memberDaniel Cohen Gindi9 Apr '10 - 2:42 
Thanks! Smile | :)
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

General[My Vote of 5]memberChris Carter26 Jan '10 - 5:25 
I've run into the same problem, I like the solution, thanks!
 
I gave ya 4 for the code and 1 for this response to the other commenter who gave you the vote of 1:
 
"This is the most idiotic response I could ask for, thanks!"
 
I almost spit out my corn flakes after reading that response, hilarious.
GeneralRe: [My Vote of 5]memberDaniel Cohen Gindi26 Jan '10 - 5:28 
Thanks bro!
Well I was right wasn't I? Wink | ;-)
 
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

General[My vote of 1] Why hack .net find a real solutionmemberRenderEngSol26 Jan '10 - 0:44 
I think you are not fully utilizing the ASP.NET framework as it should. Rather than solving the propblem you hacked into the framework and tried to develop a solution.
 
Your solutions needs some more investigation not on creation of something new but correct utilization of what the framework offers.
 
But if you do continue to use it that is your choice, but I would recommend you add some functionality to include virtual paths, port and use the Current HttpContext and the Uri class as a start.
 
HttpContext includes a static member to get the Current Request where you get the URL (Uri) of current request, providing usefull information for building your url.
GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberDaniel Cohen Gindi26 Jan '10 - 0:53 
This is the most idiotic response I could ask for, thanks!
 
Sometimes there's no 'fully utilizing' .NET framework, because it does not provide what you need. Right now I'm implementing a red eye reduction algorithm, this is not a service that .NET could give me. So by implementing that I'm 'not utilizing .NET framework as it should'? Give me a break.
This is not "hacking" into .NET, this is using the available tools to create something better.
 
Tell me how would you use ResolveUrl of .NET when you have a QueryString in your url which includes "://"! There's no correct use of that, because it is buggy! Therefore you need to implement your own method.
 
The "Current Request" is available only if there is a Request. But if there is a routine called by a timer, or some other handler which works OUTSIDE the request, then using the Request won't do nothing for you.
 
Using the Uri class - that's just stupid. Uri class is for entirely other things, it won't do nothing for you to convert resolve urls. Besides it just does too much work for what we need.
 
My algorithm is much more efficient than that, and much more efficient than .NET's natural ResolveUrl, and it does not have the bugs that .NET has.
 
So please think before your next post.
 
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberRenderEngSol26 Jan '10 - 1:14 
First, you did not ask for a response, this is a public site and it is expected.
 
Secondly, anyone that does not 'fully utilize' the .Net framework is doomed to implement poor solutions, and try to solve problems in the wrong area of their application.
 
Thirdly, what does a bug in the URLs you are sending to the server have to do with implmenting a red eye reduction algorithm, I think you need to provide a clearer sepration of conserns.
 
Fourthly, I would use ResolveURl of .Net as dictated by MSDN documentation and fix the bug in your query string generation.
 
The .Net framework if properly utilized by an experience Object Oriented Programer can do anything we need.
 
Finaly, I think you should think before you post an article entitled 'The perfect solution' you should have called it 'Alternative approach to my Bugs'.
GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberDaniel Cohen Gindi26 Jan '10 - 1:21 
You still just don't get it.
An experienced programmer will develop solutions for where there are problems. If .NET's ResolveUrl does not provide, then you should do it yourself. This is what I did.
 
MSDN or .NET are not the holy grail, they can be wrong, and they can be buggy. Microsoft have just released a patch for the whole VC++ Runtime, to recompile all software, because of a security bug. Which I've known about long ago, and made my own patches. So what are you saying, I should have just 'lived' with the bug and let it be?
 
My conclusion is - do whatever you like. You don't want to use my code, so don't. I did not force your hand. You have some strange ideas about .NET and about what a programmer should do or not do, those are your ideas. Thanks for sharing. But my experience is wide enough to make my own decisions, and I decided to write this code not for a reason.
 
Actually, I don't know why am I 'defending' myself at all. Just do what you like, use, do not use, program or not, respond or not, whatever.
 
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

AnswerRe: [My vote of 1] Why hack .net find a real solutionmemberstixoffire30 Jan '10 - 12:05 
I absolutely cannot understand your responses.
 
Dot NET has some real bugs - try putting a drop down list on a web page and findout about the bugs it comes with - if there is a solution for those bugs built into the .NET framework show it.
 
You somehow are missing the point, and the real advantage of .NET ; DOT NET is a framework - it is customizeable so if you do not have what you need or if what is provided is insufficient - you can build on what is there. OOP, Reusability and Extensibility!
 
Why do you think all of these articles are on Code Project - declaring that people searched for this or that in DOT NET - there was not one , or the one provided did not have the functionality needed or had some defect prohibiting the use of such a control in a real world application and therefore they created their own.
 
Ever use the Calenday Control, or the drop down control .. pathetic.
 
Have you ever extended the functionality of what is in a .NET control - then I guess you went around to create your own poorly devised solution.
 
Your response shows that you do not understand OOP reusability and extensability.
 
Your comments to the Author were just plain uneducated.
While the site is public intelligible and educated comments are always preferred reading - yours were unhelpful to anyone.
GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberRenderEngSol1 Feb '10 - 3:47 
I understand completely extensibility and re-usability. The author never reused, he hacked the framework using a reflection tool to solve an incorrect usage of something provided by the framework.
 
My comments to the author are educated and directed at an individuals attempts to solve a problem in a direction that I disagree with.
 
And I suppose your comments were helpful.
 
It just goes to show that everyone has two cents to share with everyone.
GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberDaniel Cohen Gindi1 Feb '10 - 7:12 
Hilarious!
 
1. The reflection tool may be called a 'hack', but it is a useful tool to learn how .NET does certain things, and to learn where are the bugs placed, so you know how to avoid them. When there's a bug - going around it or fixing it is the correct behavior.
2. Requiring a ResolveUrl outside of Request context, is never an "incorrect usage". In fact, in most of the applications which are a little more than the most basic websites, you will need a function like ResolveUrl outside the Request context.
3. Requiring a ResolveUrl which is free of bugs, or has better performance - that's good practice...
 
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberloyal ginger26 Jan '10 - 6:35 
I guess this is just life. There are critics out there who want to express their opinions. In this place, it does not matter if your article is great or not, you can always expect somebody giving you a vote of 1 or 2. As a matter of fact, in a lot of cases it is their sole job to give negative reviews -- they enjoy it. Normally this type of comments don't need any response from you. From a statistics point of view, you will get a rating that closely reflects the real value of your article. Negative reviews are not always bad -- they may draw more people to read your article, and find out if the statement is right or wrong. You may get more positive reviews out of it.
 
Don't get me wrong. I am not judging RenderEngSol's comments in this place.
 
By the way, I like your articles. They are very helpful. Thanks for your selfless contribution to the programming community.
GeneralRe: [My vote of 1] Why hack .net find a real solutionmemberMike Ellison26 Jan '10 - 6:16 
As someone not involved with this article (I did not write it, nor do I know the author), I can tell you: this is a disappointing response and doesn't do justice to what the author has presented.
 
It is disappointing because it fails to recognize how the author did investigate what was available in the framework (read the article). The author makes clear how he came up with his solution precisely because the framework's version was problematic.
 
This wasn't a hack - it is a function that provides utility where the framework didn't. Your response comes off as a knee-jerk reaction that itself fails to recognize where the framework's version came up short. It sounds like YOU need some more investigation to recognize what the framework doesn't offer.
 
It would be nice to have one catch-all ResolveUrl function that works in all cases regardless of the url you pass it; the existing version in the framework does not. The author (unless I am mistaken, and I invite him to correct me if I am) believes that is what he is presenting here with his function. If it works in all url cases, it is a solid and useful contribution.
 

Generalgood jobmemberArlen Navasartian21 Jan '10 - 9:38 
take my 5
 
-------
Arlen.N

QuestionOk who edited my article after posting???memberDaniel Cohen Gindi20 Jan '10 - 5:16 
You messed it up! You changed meaning of sentences completly!!! Please restore from the original version ASAP!
 
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com

AnswerRe: Ok who edited my article after posting???memberSandeep Mewara20 Jan '10 - 8:10 
Editor: Smitha Vijayan
Look at the left bottom of the article page!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 26 Jan 2010
Article Copyright 2010 by Daniel Cohen Gindi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid