Background

I was recently asked, for a large and fairly complex website with plenty of pages and markup, to automatically detect external links and add a rel=”nofollow” attribute to each one of them.

Aside from whether this is good practice or not from a SEO perspective, it’s a daunting task because for each one of (possibly hundreds) of asp:HyperLink server controls, I would have to check whether the NavigateUrl property points to an external link and add the nofollow attribute programmatically.

The Solution

It turns out that the System.Web.UI.WebControls.HyperLink class in the System.Web assembly is not sealed. This gives us an extension point, so that we can centralize the decision and add the attribute there. We may then register our new control in our web.config with a custom tag prefix and simply do a global replace from asp:HyperLink to custom:HyperLink.

You may browse the example page in the associated download, but the key extensibility point is the override for the AddAttributesToRender() method (commented for your pleasure). This method takes an argument of class HtmlTextWriter, and uses this argument to add the nofollow attribute if this is indeed an external URL.

The Code

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
    base.AddAttributesToRender(writer);

    // Only create the rel attribute if not explicitly defined
    if (!this.Attributes.Keys.Cast<string>().Select(i => i.ToLower()).Contains("rel"))
    {
        try
        {
            Uri url;
            if (Uri.TryCreate(this.NavigateUrl, UriKind.RelativeOrAbsolute, out url))
            {
                // If this is a relative URL, then no need to nofollow
                if (!url.IsAbsoluteUri) return;
                // If this is not an HTTP(S) URL, then no need to nofollow
                if (url.Scheme != "http" && url.Scheme != "https") return;

                if (url.Host != HttpContext.Current.Request.Url.Host)
                {
                    // This is a link to an external URL
                    writer.AddAttribute(HtmlTextWriterAttribute.Rel, "nofollow");
                }
            }
        }
        catch
        {
            // Log, recover, etc
        }
    }
}

You can download the full proof of concept solution here (8,38 kb).