Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

ResolveUrl() without Page


:P
On this page:

ASP.NET's Control class provides the handy ResolveUrl method that lets you easily use and parse relative URLs. ResolveUrl fixes up urls that start with ~ and puts the applications relative root path in its place. So if I'm in:

/wwstore/admin/Configuration.aspx

and I reference ~/images/someimage.gif ResolveUrl will return /wwstore/images/SomeImage.gif to me. This is much better than hardcoding /wwstore/images/someimage.gif or even using a relative path like ../images/someimage.gif. Using the ~ syntax makes the app much more resilient to pages or controls moving inside of the application. Most controls that have URL properties also use ResolveUrl so you can specify URLs on control properties using the same relative syntax.

Sometimes however, I find myself in some generic piece of Web code where I don't have access to the Page or a Control to actually use Control.ResolveUrl. For example, I was working on my Resource Provider recently and one of the options of the provider is to specify the resource administration for URL using - you guessed it ~ style relative path syntax. Deep inside the provider code I have no access to a page object - in fact the code may never actually hit a page handler at all but a custom HttpHandler instead.

So this issue comes up from time to time,  and it's useful to have a more generic ResolveUrl method. I thought that there was some method somewhere buried deep on HttpContext that could perform this fix up but I can't for the life of me find it. In the meantime I've been using the following code:

/// <summary>
/// Returns a site relative HTTP path from a partial path starting out with a ~.
/// Same syntax that ASP.Net internally supports but this method can be used
/// outside of the Page framework.
/// 
/// Works like Control.ResolveUrl including support for ~ syntax
/// but returns an absolute URL.
/// </summary>
/// <param name="originalUrl">Any Url including those starting with ~</param>
/// <returns>relative url</returns>
public static string ResolveUrl(string originalUrl)
{
    if (originalUrl == null)
        return null;
 
    // *** Absolute path - just return
    if (originalUrl.IndexOf("://") != -1)
        return originalUrl;
 
    // *** Fix up image path for ~ root app dir directory
    if (originalUrl.StartsWith("~"))
    {
        string newUrl = "";
        if (HttpContext.Current != null)
            newUrl = HttpContext.Current.Request.ApplicationPath +
                  originalUrl.Substring(1).Replace("//", "/");
        else
            // *** Not context: assume current directory is the base directory
            throw new ArgumentException("Invalid URL: Relative URL not allowed.");                                       
 
        // *** Just to be sure fix up any double slashes
        return newUrl;
    }
 
    return originalUrl;
}
 

You call it just like you would Control.ResolveUrl() and it will return an Application relative path.

Another related scenario is to resolve URLs into fully qualified absolute URLs. For example, in several applications I have callback URLs that get passed to various services like PayPal and Trackback services, that are supposed to call back your application. These pages need to get a fully qualified URL so they can find your page on the Web. Another scenario where I found this necessary is when you need to switch URLs into ssl as part of your application. For example, in my store you enter as a non-SSL URL until you hit the order pages at which point the page switches to SSL and you have to provide a fully qualified URL to do so.

/// <summary>
/// This method returns a fully qualified absolute server Url which includes
/// the protocol, server, port in addition to the server relative Url.
/// 
/// Works like Control.ResolveUrl including support for ~ syntax
/// but returns an absolute URL.
/// </summary>
/// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
/// <param name="forceHttps">if true forces the url to use https</param>
/// <returns></returns>
public static string ResolveServerUrl(string serverUrl, bool forceHttps)
{
    // *** Is it already an absolute Url?
    if (serverUrl.IndexOf("://") > -1)
        return serverUrl;
 
    // *** Start by fixing up the Url an Application relative Url
    string newUrl = ResolveUrl(serverUrl);
 
    Uri originalUri = HttpContext.Current.Request.Url;
    newUrl = (forceHttps ? "https" : originalUri.Scheme) + 
             "://" + originalUri.Authority + newUrl;
 
    return newUrl;
 
/// <summary>
/// This method returns a fully qualified absolute server Url which includes
/// the protocol, server, port in addition to the server relative Url.
/// 
/// It work like Page.ResolveUrl, but adds these to the beginning.
/// This method is useful for generating Urls for AJAX methods
/// </summary>
/// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
/// <returns></returns>
public static string ResolveServerUrl(string serverUrl)
{
    return ResolveServerUrl(serverUrl, false);
}

So the following applies:

// returns https://www.west-wind.com/wwStore/test.aspx
Response.Write( wwWebUtils.ResolveServerUrl("~/Test.aspx",true));

This is one of those things that's not needed frequently but when you do it's a pain to try and start remembering exactly how to retrieve these values. For me this happens often enough that I'll keep this in my toolbox.

Posted in ASP.NET  

The Voices of Reason


 

Sergio Pereira
September 18, 2007

# re: ResolveUrl() without Page

Not too long ago I was introduced to the class System.Web.VirtualPathUtility, which has proven very helpful dealing with this stuff.

Josh Stodola
September 18, 2007

# re: ResolveUrl() without Page

>> I thought that there was some method somewhere buried deep on HttpContext that could perform this fix up but I can't for the life of me find it <<

I believe you were looking for the VirtualPathUtility class.

http://msdn2.microsoft.com/en-us/library/system.web.virtualpathutility_methods.aspx

By the way, I *always* have to enter the validation code twice when commenting here...

Speednet
September 18, 2007

# re: ResolveUrl() without Page

Hey Rick,

We are so often on the same wavelength. I too just enabled a bunch of my own controls to use the virtual path character ("~").

As Sergio and Josh mentioned, the VirtualPathUtility works great for this. The statement I use is:

newUrl = VirtualPathUtility.ToAbsolute(sourceUrl);

Rick Strahl
September 18, 2007

# re: ResolveUrl() without Page

Aaargh... I knew I had missed something. I've used this class before myself but I just couldn't remember for the life of me what it was.

Anyway, it looks that this solves only a part of the issue - namely ~/ prefixed paths. Which is fine since that is the most common scenario and it just allows reducing the code in the ResolveUrl method a bit.

I still like the idea though of being able to handle both fully qualified absolute paths (ie. with http://) as well as relative paths without ~/ all from one place. For example in the scenario of the Url for a configuration page it's possible that the URL lives in a completely separate application or even server...

So I guess I'll hang on to the above with a small modifcation for the ~ operation.

public static string ResolveUrl(string originalUrl)
{
    if (originalUrl == null)
        return null;

    // *** Absolute path - just return
    if (originalUrl.IndexOf("://") != -1)
        return originalUrl;

    // *** Fix up image path for ~ root app dir directory
    if (originalUrl.StartsWith("~"))
        return VirtualPathUtility.ToAbsolute(originalUrl);

    return originalUrl;
}


The full server URL version is still required though.

Thanks all!

Speednet
September 18, 2007

# re: ResolveUrl() without Page

I would personally get rid of the second "if", because if the URL starts with "~" you always want to translate it. For example, consider the following URL, which is improperly URL encoded, but will still work:

"~/html/test.aspx?url=http://www.testuri.org/"

I cannot imagine any circumstance where the URL starts with "~" and you wouldn't want to translate it, even if it contains "://".

...constantly refactoring... ;-)

Dan Atkinson
September 19, 2007

# re: ResolveUrl() without Page

Hey there.

This is actually incredibly useful for dynamically created for stylesheets and JavaScript which are created/referenced on the code-side.

One of the problems I have in .NET is that you can reference ~ in most tags like hyperlinks and css, but not JavaScript. I've no idea why that is.

This method is particularly good for me, so that I can have websites in virtual directories which, when published, are the root.

The downside of all this is that non-.NET files (*.js, *.css, et al) are not handled the same way. For example, the url attribute in CSS. If it's an absolute url in that, it would need to be handled in a http handler.

Richard Deeming
September 19, 2007

# re: ResolveUrl() without Page

One problem with the VirtualPathUtility class is that it will throw an HttpException if the path contains a query-string, or is a full URL, whereas the ResolveUrl method won't.

public static string ResolveUrl(string originalUrl)
{
    if (!string.IsNullOrEmpty(originalUrl) && '~' == originalUrl[0])
    {
        int index = originalUrl.IndexOf('?');
        string queryString = (-1 == index) ? null : originalUrl.Substring(index);
        if (-1 != index) originalUrl = originalUrl.Substring(0, index);
        originalUrl = VirtualPathUtility.ToAbsolute(originalUrl) + queryString;
    }
    
    return originalUrl;
}


To resolve the full URL, I tend to use the Uri and UriBuilder classes:

public static string ResolveServerUrl(string serverUrl, bool forceHttps)
{
    Uri result = HttpContext.Current.Request.Url;
    if (!string.IsNullOrEmpty(serverUrl))
    {
        serverUrl = ResolveUrl(serverUrl);
        result = new Uri(result, serverUrl);
    }
    if (forceHttps && !string.Equals(result, Uri.UriSchemeHttps))
    {
        UriBuilder builder = new UriBuilder(result);
        builder.Scheme = Uri.UriSchemeHttps;
        builder.Port = 443;
        result = builder.Uri;
    }
    
    return result.ToString();
}

Rick Strahl
September 19, 2007

# re: ResolveUrl() without Page

@Dan - JavaScript is client side so doesn't have access to the server side functions. You can embed Urls from the server into the rendered page however like this:

var url = '<%= ResolveUrl("~/images/wait.gif") %>';


but that obviously only works for Urls that are server static.

@Richard - thanks for the querystring reminder. Very good point in fact. The more generic the better <s>...

John Vogel
September 20, 2007

# re: ResolveUrl() without Page

I can't find the answer to this on google - Why do people check if HttpContext.Current is null? My thoughts are that how else would a method like this get called? Also, .NET is probably going to make sure the connection is open before it writes a response, and if not no one would be there to see the error. The only answer I've gotten so far was that in case these methods were in a .DLL or something and called by a windows application, etc. Thanks for any insight you have on this.

Rick Strahl
September 20, 2007

# re: ResolveUrl() without Page

@John - At design time there's no HttpContext.Current. So if you're building a control or other system component like a resource provider there's no guarantee that HttpContext.Current is actually there...

Gary Davis
October 01, 2007

# re: ResolveUrl() without Page

I am trying to port code to IIS7 and found that testing for null does not work like it did in iis6. The HttpContext.Current can be non-null but the Request property is not there, throwing an exception.

if (HttpContext.Current != null)
            newUrl = HttpContext.Current.Request.ApplicationPath +                  originalUrl.Substring(1).Replace("//", "/");


Gary Davis

alhambra eidos
October 03, 2007

# re: ResolveUrl() without Page

Hi mister,

which is the best version of those methods ?? mister Richard Deeming's version ???

It would be interesting if the right (best) version is R.Deeming's code, you can update it in this post.

Thanks in advance, best regards.

Donnie Hale
October 12, 2007

# re: ResolveUrl() without Page

Here's a little trick I discovered today. You can use the constructor of the Uri class to help convert relative URLs (e.g. no leading '/' or a leading '../') if you have an appropriate full URL (e.g. "http://mysite.com/").

Uri uriFull = new Uri(uriReferer, urlRelative);

I've used this code in a console-based utility I'm writing to follow the links in my web app, and it's yet to do the wrong thing. If the relative URL starts with "..", the referer obviously has to be an appropriate path. If it's not, you may have found a bad link in your app (like something hard-coded as "../whatever" when "~/path/whatever" should be used).

What I'd really like now is a way to force Page.ResolveUrl to always return its URLs as an app-absolute path (i.e. always with a leading '/'). It frequently will return relative paths like "../images/mypic.gif", and I'd prefer to see "/myapp/images/mypic.gif"). Obviously, I can't hit everyplace in the code to make a change to effect this, so it would be great if there were a Web.config setting or something like that to get that behavior.

FWIW...

Donnie

manovich
October 31, 2007

# re: ResolveUrl() without Page

> What I'd really like now is a way to force Page.ResolveUrl to always return its URLs as an app-absolute path (i.e. always with a leading '/'). It frequently will return relative paths like "../images/mypic.gif", and I'd prefer to see "/myapp/images/mypic.gif"). Obviously, I can't hit everyplace in the code to make a change to effect this, so it would be great if there were a Web.config setting or something like that to get that behavior.


Actually ResolveUrl should always return absolute url (with leading "/") and should return ResolveClientUrl returns relative url.

Talking about VirtualPathUtility.ToAbsolute() and uri with query string. Another available option is to use HttpRuntime.AppDomainAppVirtualPath (http://dotnettipoftheday.org/tips/HttpRuntime.AppDomainAppVirtualPath.aspx)

AC
January 22, 2008

# re: ResolveUrl() without Page

Here's my take on the whole package. I added Deeming's code to Strahl's original code to get the best of both worlds.

I found Deeming's replacement code to introduce some inconsistencies such as rewriting url's that are already absolute to using https, which I felt was outside the scope of this classes responsibility. I liked Strahl's assumption that if a Url is already absolute, do nothing with it.

Additionally, Strahls's original code for forcing https looked like it might fail if called on a server using a non-standard port, because the originalUri.Authority docs state that this uses DNS or IP *and* the port name for a server. i.e. https://somemachine:8080/path/file.aspx. I didn't test this assertion, but instead I opted to use the UriBuilder which should do the right thing.

PS from a style perspective, I altered Deeming's code to stop overwriting variables that were passed in because I find it quite hard to follow otherwise.

        /// <summary>
        /// Returns a site relative HTTP path from a partial path starting out with a ~.
        /// Same syntax that ASP.Net internally supports but this method can be used
        /// outside of the Page framework.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="originalUrl">Any Url including those starting with ~</param>
        /// <returns>relative url</returns>
        public static string ResolveUrl(string originalUrl)
        {
            if (string.IsNullOrEmpty(originalUrl))
                return originalUrl;

            // *** Absolute path - just return
            if (originalUrl.IndexOf("://") != -1)
                return originalUrl;

            // *** Fix up path for ~ root app dir directory
            if (originalUrl.StartsWith("~"))
            {
                // VirtualPathUtility blows up if there is a query string, so we
                // have to account for this.
                int queryStringStartIndex = originalUrl.IndexOf('?');
                if (queryStringStartIndex != -1)
                {
                    string queryString = originalUrl.Substring(queryStringStartIndex);
                    string baseUrl = originalUrl.Substring(0, queryStringStartIndex);

                    return VirtualPathUtility.ToAbsolute(baseUrl) + queryString;
                }
                else 
                {
                    return VirtualPathUtility.ToAbsolute(originalUrl);
                }
            }

            return originalUrl;
        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <param name="forceHttps">if true forces the url to use https</param>
        /// <returns></returns>
        public static string ResolveServerUrl(string serverUrl, bool forceHttps)
        {
            if (string.IsNullOrEmpty(serverUrl))
                return serverUrl;

            // *** Is it already an absolute Url?
            if (serverUrl.IndexOf("://") > -1)
                return serverUrl;
            
            string newServerUrl = ResolveUrl(serverUrl);
            Uri result = new Uri(HttpContext.Current.Request.Url, newServerUrl);

            if (forceHttps)
            {
                UriBuilder builder = new UriBuilder(result);
                builder.Scheme = Uri.UriSchemeHttps;
                builder.Port = 443;

                result = builder.Uri;
            }

            return result.ToString();
        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// It work like Page.ResolveUrl, but adds these to the beginning.
        /// This method is useful for generating Urls for AJAX methods
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <returns></returns>
        public static string ResolveServerUrl(string serverUrl)
        {
            return ResolveServerUrl(serverUrl, false);
        }

AC
January 23, 2008

# re: ResolveUrl() without Page

I couldn't help myself to change the code just a little bit. Nah, I'm no perfectionist ;-)

Small refactorings to make it more readable. You could always collapse the initial if's into one, but it's all the same (and would likely be optimised that way)

Also, here's the table of results:

~/SomePage.aspx /WebSite/SomePage.aspx
~/SomePage.aspx http://localhost/WebSite/SomePage.aspx
~/SomePage.aspx https://localhost/WebSite/SomePage.aspx
~/With/Path.aspx /WebSite/With/Path.aspx
~/With/Path.aspx http://localhost/WebSite/With/Path.aspx
~/With/Path.aspx https://localhost/WebSite/With/Path.aspx
~/WithQuery.aspx?id=1234&page=2 /WebSite/WithQuery.aspx?id=1234&page=2
~/WithQuery.aspx?id=1234&page=2 http://localhost/WebSite/WithQuery.aspx?id=1234&page=2
~/WithQuery.aspx?id=1234&page=2 https://localhost/WebSite/WithQuery.aspx?id=1234&page=2
http://absolute.net/page.aspx http://absolute.net/page.aspx
http://absolute.net/page.aspx http://absolute.net/page.aspx
http://absolute.net/page.aspx http://absolute.net/page.aspx
/Already/Root/Relative.aspx /Already/Root/Relative.aspx
/Already/Root/Relative.aspx http://localhost/Already/Root/Relative.aspx
/Already/Root/Relative.aspx https://localhost/Already/Root/Relative.aspx

and the new code:

        /// <summary>
        /// Returns a site relative HTTP path from a partial path starting out with a ~.
        /// Same syntax that ASP.Net internally supports but this method can be used
        /// outside of the Page framework.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="originalUrl">Any Url including those starting with ~</param>
        /// <returns>relative url</returns>
        public static string ResolveUrl(string originalUrl)
        {
            if (string.IsNullOrEmpty(originalUrl))
                return originalUrl;

            // *** Absolute path - just return
            if (originalUrl.IndexOf("://") != -1)
                return originalUrl;

            // *** We don't start with the '~' -> we don't process the Url
            if (!originalUrl.StartsWith("~"))
                return originalUrl;

            // *** Fix up path for ~ root app dir directory
            // VirtualPathUtility blows up if there is a 
            // query string, so we have to account for this.
            int queryStringStartIndex = originalUrl.IndexOf('?');
            if (queryStringStartIndex != -1)
            {
                string queryString = originalUrl.Substring(queryStringStartIndex);
                string baseUrl = originalUrl.Substring(0, queryStringStartIndex);

                return string.Concat(
                    VirtualPathUtility.ToAbsolute(baseUrl), 
                    queryString);
            }
            else 
            {
                return VirtualPathUtility.ToAbsolute(originalUrl);
            }

        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <param name="forceHttps">if true forces the url to use https</param>
        /// <returns></returns>
        public static string ResolveServerUrl(string serverUrl, bool forceHttps)
        {
            if (string.IsNullOrEmpty(serverUrl))
                return serverUrl;

            // *** Is it already an absolute Url?
            if (serverUrl.IndexOf("://") > -1)
                return serverUrl;
            
            string newServerUrl = ResolveUrl(serverUrl);
            Uri result = new Uri(HttpContext.Current.Request.Url, newServerUrl);

            if (!forceHttps)
                return result.ToString();
            else
                return ForceUriToHttps(result).ToString();

        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// It work like Page.ResolveUrl, but adds these to the beginning.
        /// This method is useful for generating Urls for AJAX methods
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <returns></returns>
        public static string ResolveServerUrl(string serverUrl)
        {
            return ResolveServerUrl(serverUrl, false);
        }

        /// <summary>
        /// Forces the Uri to use https
        /// </summary>
        private static Uri ForceUriToHttps(Uri uri)
        {
            // ** Re-write Url using builder.
            UriBuilder builder = new UriBuilder(uri);
            builder.Scheme = Uri.UriSchemeHttps;
            builder.Port = 443;

            return builder.Uri;
        }

Sam
February 06, 2008

# re: ResolveUrl() without Page

I have referred to your articles many times. Always informative and well written.

Thanks.

Wiktor Zychla
April 23, 2008

# re: ResolveUrl() without Page

there's still a small bug in the code, in both ResolveUrl and ResolveServerUrl method.

// *** Absolute path - just return
if (originalUrl.IndexOf("://") != -1)
  return originalUrl;


the cause of the bug is just here. you see, there are cases when the absolute path specifier exists in the path beeing resolved but it is a part of a querystring parameter:

~/pathtoresolve/page.aspx?returnurl=http://servertoredirect/resource.aspx

in fact what you should then do is not only to check if the url contains the :// but also if it contains the query string parameter separator, "?", positioned BEFORE the ://.

my quick hack around this is:

        /// <summary>
        /// Returns a site relative HTTP path from a partial path starting out with a ~.
        /// Same syntax that ASP.Net internally supports but this method can be used
        /// outside of the Page framework.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="originalUrl">Any Url including those starting with ~</param>
        /// <returns>relative url</returns>
        public static string ResolveUrl( string originalUrl )
        {
            if ( string.IsNullOrEmpty( originalUrl ) )
                return originalUrl;

            // *** Absolute path - just return
            int IndexOfSlashes       = originalUrl.IndexOf( "://" );
            int IndexOfQuestionMarks = originalUrl.IndexOf( "?" );

            if ( IndexOfSlashes > -1 &&
                 ( IndexOfQuestionMarks < 0 ||
                  ( IndexOfQuestionMarks > -1 && IndexOfQuestionMarks > IndexOfSlashes )
                  )
                )
                return originalUrl;

            // *** We don't start with the '~' -> we don't process the Url
            if ( !originalUrl.StartsWith( "~" ) )
                return originalUrl;

            // *** Fix up path for ~ root app dir directory
            // VirtualPathUtility blows up if there is a 
            // query string, so we have to account for this.
            int queryStringStartIndex = originalUrl.IndexOf( '?' );
            if ( queryStringStartIndex != -1 )
            {
                string queryString = originalUrl.Substring( queryStringStartIndex );
                string baseUrl = originalUrl.Substring( 0, queryStringStartIndex );

                return string.Concat(
                    VirtualPathUtility.ToAbsolute( baseUrl ),
                    queryString );
            }
            else
            {
                return VirtualPathUtility.ToAbsolute( originalUrl );
            }

        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// Works like Control.ResolveUrl including support for ~ syntax
        /// but returns an absolute URL.
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <param name="forceHttps">if true forces the url to use https</param>
        /// <returns></returns>
        public static string ResolveServerUrl( string serverUrl, bool forceHttps )
        {
            if ( string.IsNullOrEmpty( serverUrl ) )
                return serverUrl;

            // *** Is it already an absolute Url?
            int IndexOfSlashes       = serverUrl.IndexOf( "://" );
            int IndexOfQuestionMarks = serverUrl.IndexOf( "?" );

            if ( IndexOfSlashes > -1 &&
                 ( IndexOfQuestionMarks < 0 ||
                  ( IndexOfQuestionMarks > -1 && IndexOfQuestionMarks > IndexOfSlashes )
                  )
                )
                return serverUrl;

            string newServerUrl = ResolveUrl( serverUrl );
            Uri result = new Uri( HttpContext.Current.Request.Url, newServerUrl );

            if ( !forceHttps )
                return result.ToString();
            else
                return ForceUriToHttps( result ).ToString();

        }

        /// <summary>
        /// This method returns a fully qualified absolute server Url which includes
        /// the protocol, server, port in addition to the server relative Url.
        /// 
        /// It work like Page.ResolveUrl, but adds these to the beginning.
        /// This method is useful for generating Urls for AJAX methods
        /// </summary>
        /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
        /// <returns></returns>
        public static string ResolveServerUrl( string serverUrl )
        {
            return ResolveServerUrl( serverUrl, false );
        }

        /// <summary>
        /// Forces the Uri to use https
        /// </summary>
        private static Uri ForceUriToHttps( Uri uri )
        {
            // ** Re-write Url using builder.
            UriBuilder builder = new UriBuilder( uri );
            builder.Scheme = Uri.UriSchemeHttps;
            builder.Port = 443;

            return builder.Uri;
        }

Darragh Jones
July 30, 2008

# re: ResolveUrl() without Page

Would someone be able to update this class to handle the case where the session is configured with cookieless=true?

Chuck Wagner
August 28, 2008

# re: ResolveUrl() without Page

slight change to Wiktor and AC's code...now we can all argue about the implementation of IsAbsolutePath ()

    /// <summary>
    /// Returns a site relative HTTP path from a partial path starting out with a ~.
    /// Same syntax that ASP.Net internally supports but this method can be used
    /// outside of the Page framework.
    /// 
    /// Works like Control.ResolveUrl including support for ~ syntax
    /// but returns an absolute URL.
    /// </summary>
    /// <param name="originalUrl">Any Url including those starting with ~</param>
    /// <returns>relative url</returns>
    public static string ResolveUrl(string originalUrl)
    {
        if (string.IsNullOrEmpty(originalUrl))
            return originalUrl;

        // *** Absolute path - just return
        if (IsAbsolutePath(originalUrl))
            return originalUrl;

        // *** We don't start with the '~' -> we don't process the Url
        if (!originalUrl.StartsWith("~"))
            return originalUrl;

        // *** Fix up path for ~ root app dir directory
        // VirtualPathUtility blows up if there is a 
        // query string, so we have to account for this.
        int queryStringStartIndex = originalUrl.IndexOf('?');
        if (queryStringStartIndex != -1)
        {
            string queryString = originalUrl.Substring(queryStringStartIndex);
            string baseUrl = originalUrl.Substring(0, queryStringStartIndex);

            return string.Concat(
                VirtualPathUtility.ToAbsolute(baseUrl),
                queryString);
        }
        else
        {
            return VirtualPathUtility.ToAbsolute(originalUrl);
        }

    }

    /// <summary>
    /// This method returns a fully qualified absolute server Url which includes
    /// the protocol, server, port in addition to the server relative Url.
    /// 
    /// Works like Control.ResolveUrl including support for ~ syntax
    /// but returns an absolute URL.
    /// </summary>
    /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
    /// <param name="forceHttps">if true forces the url to use https</param>
    /// <returns></returns>
    public static string ResolveServerUrl(string serverUrl, bool forceHttps)
    {
        if (string.IsNullOrEmpty(serverUrl))
            return serverUrl;

        // *** Is it already an absolute Url?
        if(IsAbsolutePath(serverUrl))
            return serverUrl;

        string newServerUrl = ResolveUrl(serverUrl);
        Uri result = new Uri(HttpContext.Current.Request.Url, newServerUrl);

        if (!forceHttps)
            return result.ToString();
        else
            return ForceUriToHttps(result).ToString();

    }

    /// <summary>
    /// This method returns a fully qualified absolute server Url which includes
    /// the protocol, server, port in addition to the server relative Url.
    /// 
    /// It work like Page.ResolveUrl, but adds these to the beginning.
    /// This method is useful for generating Urls for AJAX methods
    /// </summary>
    /// <param name="ServerUrl">Any Url, either App relative or fully qualified</param>
    /// <returns></returns>
    public static string ResolveServerUrl(string serverUrl)
    {
        return ResolveServerUrl(serverUrl, false);
    }

    /// <summary>
    /// Forces the Uri to use https
    /// </summary>
    private static Uri ForceUriToHttps(Uri uri)
    {
        // ** Re-write Url using builder.
        UriBuilder builder = new UriBuilder(uri);
        builder.Scheme = Uri.UriSchemeHttps;
        builder.Port = 443;

        return builder.Uri;
    }

    private static bool IsAbsolutePath(string originalUrl)
    {
        // *** Absolute path - just return
        int IndexOfSlashes = originalUrl.IndexOf("://");
        int IndexOfQuestionMarks = originalUrl.IndexOf("?");

        if (IndexOfSlashes > -1 &&
             (IndexOfQuestionMarks < 0 ||
              (IndexOfQuestionMarks > -1 && IndexOfQuestionMarks > IndexOfSlashes)
              )
            )
            return true;

        return false;
    }

Luis
September 02, 2008

# re: ResolveUrl() without Page

Awesome snippet of code! Thanks.

tom
October 24, 2008

# re: ResolveUrl() without Page

why dont you use the Pages Resolve url? I had the same problem, and first of all I used your approach... but you can get very easy to the Page object:

Page p = HttpContext.Current.Handler as Page;


That should be all. Maybe its better, if you add a check for null, but it's for me to easiest solution.

But thanks for your snippet ;)

Rick Strahl
October 24, 2008

# re: ResolveUrl() without Page

@Tom - you're missing the point. If you are in a custom handler or inside of module code there may not be a Page class to call ResolveUrl() on.

My Personal Blog
November 22, 2009

# ResolveUrl Without Page/Control Object

ResolveUrl Without Page/Control Object

Mark Kamoski
November 25, 2009

# re: ResolveUrl() without Page

Rick (and all) --

Maybe this is naive, but why not just pass in...

this.Page

...when needed?

That is really just a point to the Page object, is it not, so the overhead is minimal?

I suppose we can say "do not break the purity of the BusinessLayer responsibility by passing a PresentationLayer object into it", which is a purist view that I can see-- but, aside from that, why not pass down "this.Page" as necessary?

Please advise.

Thank you.

-- Mark Kamoski

Mark Kamoski
November 25, 2009

# re: ResolveUrl() without Page

Rick --

BTW, this page gave me the following RTE in IE8....

Line: 1206

Error: 'get(...)' is null or not an object

...so I thought you should know.

HTH.

Thank you.

-- Mark Kamoski

JoeFV
June 14, 2010

# re: ResolveUrl() without Page

I've always been a fan of a public property returning the root of the entire site and then building the full url from there. I find my servers and domain names change far more often than the file structure. This code will handle the same site on different servers (e.g. dev, test, prod).

Rick Strahl's Web Log has been very helpful in the past so I thought I'd post my solution here. There's probably a thousand better ways to do it but posting something that may be helpful is better than nothing at all!

public class MyClass
{
    public static string URLRoot
    {
        get
        {
            string CurrentURL = HttpContext.Current.Request.Url.ToString();
            if (CurrentURL.IndexOf("?") == -1)
            {
                CurrentURL = CurrentURL + "?";
            }
 
            string CurrentFile = HttpContext.Current.Request.CurrentExecutionFilePath.ToString();
            if (CurrentFile.IndexOf("?") == -1)
            {
                CurrentFile = CurrentFile + "?";
            }
 
            string CurrentQueryString = HttpContext.Current.Request.QueryString.ToString();
 
            if (!MyClass.ValueIsEmpty(CurrentQueryString))
            {
                CurrentURL = CurrentURL.Replace(CurrentQueryString, "");
            }
            if (!MyClass.ValueIsEmpty(CurrentFile))
            {
                CurrentURL = CurrentURL.Replace(CurrentFile, "");
            }
 
            string VirtualDirectory = HttpContext.Current.Request.ApplicationPath.ToString();
 
            CurrentURL = CurrentURL + VirtualDirectory + "/";
            CurrentURL = CurrentURL.Replace("//", "/");
            CurrentURL = CurrentURL.Replace("http:/", "http://");
            CurrentURL = CurrentURL.Replace("https:/", "https://");
 
            return CurrentURL;
        }
    }
    public static bool ValueIsEmpty(string StringValue)
    {
        if (StringValue == String.Empty || StringValue == null || StringValue.Length < 1 || StringValue == "")
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}


To use this, just put something like the following in your code behind:

Image1.ImageUrl = MyClass.URLRoot + "img/myimage.png";

Luzie
August 20, 2010

# re: ResolveUrl() without Page

Hey,

this blog was very helpful for me.

I had the problem that sessionid (cookieless=true) changed if i use resolveurl in my class.
But someone postet following code in one of the comments

Dim p As Page = HttpContext.Current.Handler


and this was very very helpful for me.

Many thanks
Luzie (Frankfurt, Germany)

Kumar
August 30, 2010

# re: ResolveUrl() without Page

Hi,

This is very helpfull code, can you tell me how to do the same with the URL in Iframe,
i mean resolveURL w.r.t Iframe URL would be very helpfull,

i tried iFrame.ResolveUrl(relativeUrl), but no luck...

Thanks,
Kumar

atticae
January 29, 2011

# Problem with your code formatting

Hi Rick, could you change the way code is formatted in html in your blog?
It makes the code really annoying to copy-paste.

See: http://stackoverflow.com/questions/4838005/vs2010-adds-additional-white-lines-between-pasted-code

Apart from that: Great blog, I love it. :)

Cristian
February 08, 2011

# re: ResolveUrl() without Page

did many combinatorical attempts but none worked in the way I want.

I want to include both LaTeX code snippet and C# code snippet in my little book about LaTeX. However, I could not find the way to fix margin overflow for LTXexample.

Please see my screenshot below, you will get the idea:

alt text

And the "minimal code" is given as follows:<a href="http: peelingacne.com // ">peelingacne</a> because there is no "showexpl" tag, so I choose "listings and margins" tags.

EDIT 1: There is no margin overflow for lstlisting. I can control lstlisting margins using lstset as given in the code snippet above.

Cameron
September 13, 2011

# re: ResolveUrl() without Page

I use this often enough that I have made it an extension method:

        /// <summary>
        /// Resolve client URLs without a page context
        /// </summary>
        /// <param name="vobjContext">The </param>
        /// <param name="vstrOriginalUrl"></param>
        /// <returns></returns>
        public static string ResolveUrl(this HttpContext vobjContext, string vstrOriginalUrl)
        {
            // empty url
            if (string.IsNullOrEmpty(vstrOriginalUrl))
            {
                return vstrOriginalUrl;
            }
 
            // absolute path
            if (vstrOriginalUrl.Contains("://"))
            {
                return vstrOriginalUrl;
            }
 
            // fix ~ Urls
            if (vstrOriginalUrl.StartsWith("~"))
            {
                if (vobjContext != null && vobjContext.Request != null)
                {
 
                    return vobjContext.Request.ApplicationPath + vstrOriginalUrl.Substring(1).Replace("//", "/");
                }
                else
                {
                    throw new ArgumentException("URL Resolve failed - possible bad HttpContext.");
                }
            }
 
 
            // catch all
            return vstrOriginalUrl;
 
        }

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024