I have recently been involved in some SEO work on a website written in DotNetNuke
. SEO (Search Engine Optimization) is the arcane and mystical art of making your website simultaneously appear interesting/relevant to both users and and search engine bots, most especially the GoogleBot
. One of the keys to this it not to have duplicate content on your website, that is to say, pages that appear to be the same that are available via different URLs. If you do this, the GoogleBot will think you are trying to trick it and it devalues the relevance of that content. As a result, if you move content from one URL to another you need to 301
(permanently redirect) the old URL to the new URL.
A common way to do this is to buy or write a DNN module that redirects
and install it on your site on the pages that should be redirected. This is a pain though because it means that you have to keep those pages in your portal even though you don’t need them any more and each request to them has to load the page and the module before it is redirected.
The fact is that DNN is constantly internally redirecting anyway using a technique called URL rewriting
. The "real" address of most pages in a DNN portal is /Default.aspx?tabid=1234 where 1234 is a unique identifier for a "page" of content in DNNs database. This rewriting is done by an HttpModule
whose configuration is stored in an xml file in the root of the portal (/SiteURLs.config). An example of a URLRewriter rule can be seen here :
It occurred to me that it would be easier just to hijack this mechanism and with a very small tweak add permanent (301) and temporary (302) redirects to its features. A new rule would look something like this :
The Type attribute could have values of Permanet, Temporary or Rewrite (the default) or be absent. We then need to make a couple of simple changes to the URLRewriter module which, thanks to open source, we can.
Someone emailed me about this so I thought I’d add a quick clarification. The URLRewriter can only rewrite the path segment of the URL. So if you give it a rule that looks like this :
it will instead perform a Response.Redirect
. This sends a 302 to the client, not a 301, so without this modification we still have no way of sending a 301 and we can only send a 302 if we include the whole url, i.e. we have limited control over how the rewriter behaves.
First we add an enum to represent our rule type in RewriterRule.vb:
Public Enum RewriterRuleType
Rewrite = 0
Permanent = 1
Temporary = 2
Then we add the new public property and private field to the RewriterRule class :
Private _ruleType As RewriterRuleType = RewriterRuleType.Rewrite
Public Property RuleType() As RewriterRuleType
Set(ByVal Value As RewriterRuleType)
_ruleType = Value
We also need to modify the GetConfig method of the RewriteConfiguration class so it knows about our new XML attribute :
For Each nav As XPathNavigator In doc.CreateNavigator.Select("RewriterConfig/Rules/RewriterRule")
Dim rule As New RewriterRule()
rule.LookFor = nav.SelectSingleNode("LookFor").Value
rule.SendTo = nav.SelectSingleNode("SendTo").Value
' >>> begin new code
rule.RuleType = CType(System.Enum.Parse(GetType(RewriterRuleType), nav.GetAttribute("Type", ""), False), RewriterRuleType)
Catch ex As Exception
' Do nothing, either there was no attribute or it contained an invalid value
' NB : yes this is a dirty hack but this is a blog post not production code
' >>> end new code
Next we need to modify the RewriteURL method of the URLRewriteModule class so it can take different actions based on the newly added Rule Type :
(NB : the ruleType variable has been declared at the top of the method and is assigned to as soon as a matching rule is found.)
intMatch <> -1 Then
' >>> begin new code
Select Case (ruleType)
' >>> begin old code
If rules(intMatch).SendTo.StartsWith("~") Then
' rewrite the URL for internal processing
' it is not possible to rewrite the domain portion of the URL so redirect to the new URL
' >>> end old code
' >>> end new code
Finally, we add these new actions to the RewriterUtils class :
Shared Sub TemporarilyRedirectURL(ByVal context As HttpContext, ByVal sendToUrl As String)
context.Response.Status = "302 Found"
Friend Shared Sub PermanentlyRedirectURL(ByVal context As HttpContext, ByVal sendToUrl As String)
context.Response.Status = "301 Moved Permanently"
We can then compile the DNN source and replace the DotNetNuke.HttpModules.UrlRewrite.dll in our DNN installation with the newly compiled one.