Thursday, July 8, 2010

ASP.NET – Convert Relative URL to Absolute

See my follow up post for an alternative ToAbsoluteUrl() which accepts file-system-relative paths instead of app-relative paths: ASP.NET – Convert Relative URL to Absolute (Part 2).

Having built a blogging engine into my content management system at work, one thing I have to deal with in terms of the blog is making sure that any blog posts with references to website content make use of a fully-qualified, absolute URLs when they are rendered in the RSS feed.

Here’s two extension methods that will convert relative URLs into absolute URLs which include the full host name and port (if applicable). The first method will take a single app-relative URL (~/path/to/foo) and fully-qualify it, while the second method accepts a block of HTML and uses regular expressions to match HTML attributes which contain root-relative URLs (/path/to/foo) and replaces them using the first method.

public static string ToAbsoluteUrl(this string relativeUrl) {
    if (string.IsNullOrEmpty(relativeUrl))
        return relativeUrl;

    if (HttpContext.Current == null)
        return relativeUrl;

    if (relativeUrl.StartsWith("/"))
        relativeUrl = relativeUrl.Insert(0, "~");
    if (!relativeUrl.StartsWith("~/"))
        relativeUrl = relativeUrl.Insert(0, "~/");

    var url = HttpContext.Current.Request.Url;
    var port = url.Port != 80 ? (":" + url.Port) : String.Empty;

    return string.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, VirtualPathUtility.ToAbsolute(relativeUrl));
}
public static string HtmlAppRelativeUrlsToAbsoluteUrls(this string html) {
    if (string.IsNullOrEmpty(html))
        return html;

    const string htmlPattern = "(?<attrib>\\shref|\\ssrc|\\sbackground)\\s*?=\\s*?"
                              + "(?<delim1>[\"'\\\\]{0,2})(?!#|http|ftp|mailto|javascript)"
                              + "/(?<url>[^\"'>\\\\]+)(?<delim2>[\"'\\\\]{0,2})";

    var htmlRegex = new Regex(htmlPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
    html = htmlRegex.Replace(html, m => htmlRegex.Replace(m.Value, "${attrib}=${delim1}" + ("~/" + m.Groups["url"].Value).ToAbsoluteUrl() + "${delim2}"));

    const string cssPattern = "@import\\s+?(url)*['\"(]{1,2}"
                              + "(?!http)\\s*/(?<url>[^\"')]+)['\")]{1,2}";

    var cssRegex = new Regex(cssPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
    html = cssRegex.Replace(html, m => cssRegex.Replace(m.Value, "@import url(" + ("~/" + m.Groups["url"].Value).ToAbsoluteUrl() + ")"));

    return html;
}

Sources

6 comments:

Kurt said...

Is this implying that such methods don't provide desired functionality:

VirtualPathUtility.ToAbsolute("~/folder/file.aspx");

and/or,

Page.ResolveUrl("~/folder/file.aspx");

Nathan Taylor said...

Kurt not at all, VirtualPathUtility.ToAbsolute() and Page.ResolveUrl() serve a different purpose; specifically, they resolve a URL relative to the application only.

The above ToAbsoluteUrl() will take '~/path/to/foo' and return a globally accessible 'http://www.yourwebsite.com/path/to/foo'.

Willem said...

Works like a charm, thanks :)

Obaid said...

Old post but will give it a try.

My question is what if any are the benefits of using a full url compared to relative url?
Any SEO benefits?

It's great during development as I upgrade my website from asp.net 1.1 to 4.5; I simply copy and "Paste Alternative" in VWD 2012 and every relative url automatically becomes a full url. So all resources e.g. images just show up on my new page. and this is fine because I will eventually be replacing the old pages but keeping the directory structure when I upload. So I can concentrate on developing MasterPages and adding Ajax features.

Nathan Taylor said...

@Obaid This is not so much an SEO topic as it is related to syndication. If a blog post is using relative urls like "/path/to/article" and that post is being consumed by an RSS reader, then clicking a relative link inside of a feed item is not going to navigate a user to my website.

مجتبی said...

Nice! it solved my problem Thanks a lot!