Short URL Rewriting BlogEngine.NET

4. December 2009

Since beginning this blog one of my goals was to eventually rewrite the URLs. I've recently been successful and now you're going to learn how (for version 1.5). ;) When patching borrowed software I strive for minimal changes. This is also my first foray into this subject, so as usual "it works, but it may not be right".

This particular method uses the IIS7 URL Rewrite 1.1 module. If you're hosted on an earlier version of IIS and appreciate problem solving, request to be transferred to an IIS7 server if offered, or find another method. Thanks to IIS7's web application pool isolation this is possible even with shared hosting.

Assuming your host/you have successfully installed the URL Rewrite module, the rest will be easy (though figuring it out wasn't). Here is the <rewrite> rule section to add to your site's root Web.Config:

<system.webServer>
<rewrite>
<rules>
<rule name="post redirect">
<match url="^post/(.*)\.aspx$" />
<action type="Redirect" url="{R:1}" redirectType="Found"/>
</rule>
<rule name="post rewrite">
<match url="^([0-9]{4}/[0-9]{2}/[0-9]{2})/(.*)$" />
<action type="Rewrite" url="post.aspx?date={R:1}&amp;slug={R:2}" />
</rule>
<rule name="blog redirect">
<match url="^blog/(.*)$" />
<conditions>
<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/admin/.*$" negate="true" />
<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/User controls/.*$" negate="true" />
</conditions>
<action type="Redirect" url="{R:1}" redirectType="Found"/>
</rule>
<rule name="blog rewrite">
<match url="^(.*)$" />
<conditions>
<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/admin/.*$" negate="true" />
<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/User controls/.*$" negate="true" />
</conditions>
<action type="Rewrite" url="blog/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
  • The blog redirect & rewrite rules strip out the /blog web application folder from the URL. If your virtual directory is named otherwise, you will need to make that adjustment in the match, condition, and rewrite URLs. The redirect rule ensures that anyone visiting the old URL is instead taken to the new one, and the rewrite URL makes the new one actually function.

    You will note that the /admin and /User controls (extensions) folders have been excluded from this substitution, and that is because it's necessary or they will cease functioning. (Though probably fixable, I haven't yet bothered.)

  • The post rules perform a similar redirect and rewrite, stripping out the /post subdirectory, and trailing .aspx suffix allowing post links to be in the following form: http://codeoptimism.net/2009/12/04/Short-URL-Rewriting-BlogEngineNET
    (/blog would be in there without the other rule).

Here's where things get interesting. You may be wondering why the post rewrite rule isn't simply adding /post and .aspx in a simplistic and peaceful reflection of the post redirect rule. Ah, if only it were so easy.

BlogEngine performs its own fun rewriting in BlogEngine.Core\Web\HttpModules\UrlRewrite.cs, and the Blog-Post.aspx pages themselves are actually faked. The true URLs are in the form http://codeoptimism.net/blog/post.aspx?id=686a72df-15cb-48bb-8f56-b40ffddb6af5. So why didn't they simply drop the .aspx themselves, or why can't I modify it to do so? I believe the answer is that without the .aspx extension the server fails to direct traffic to the web app whatsoever, and that's beyond my familiarity/access.

So there still shouldn't be a problem, the .aspx would be added by the rewrite rule, right? Wrong. The rewrite is a rewrite, not a redirect, it only affects the URLs appearance and you'd be in for a nasty 404. (And before you say it, using a redirect is square one, precluding the formatting we seek.)

Since I lack the wisdom to correct this problem in any other manner, I simply patched BlogEngine.Web\post.aspx.cs to accept my own (.aspx included) rewritable format: post.aspx?date=2009/12/04&slug=Short-URL-Rewriting-BlogEngineNET.

28 28                     Response.AppendHeader("location", post.RelativeLink.ToString());
29 29                     Response.End();
30 30                 }
31 31             }
32 32         }
33 33  
  34         string qDate = Request.QueryString["date"];
  35         string qSlug = Request.QueryString["slug"];
  36  
34           if (Request.QueryString["id"] != null && Request.QueryString["id"].Length == 36)
  37         bool fromId = (Request.QueryString["id"] != null && Request.QueryString["id"].Length == 36);
  38         if (fromId)
35 39         {
36 40             Guid id = new Guid(Request.QueryString["id"]);
37 41             this.Post = Post.GetPost(id);
  42         } else if (qDate != null && qSlug != null)
  43         {
  44             DateTime date = DateTime.Parse(qDate);
  45             string slug = qSlug;
  46             this.Post = Post.Posts.Find(delegate(Post p)
  47             {
  48                 if (date != DateTime.MinValue &&
  49                     (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month))
  50                 {
  51                     if (p.DateCreated.Day != 1 && p.DateCreated.Day != date.Day)
  52                         return false;
  53                 }
  54  
  55                 return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
  56             });
  57         }
38 58  
39 59         if (Post != null)
40 60         {
41 61             if (!this.Post.IsVisible && !Page.User.Identity.IsAuthenticated)
42 62                 Response.Redirect(Utils.RelativeWebRoot + "error404.aspx", true);
43 63  
 
80 100             base.AddGenericLink("application/rss+xml", "alternate", Server.HtmlEncode(Post.Title) + " (RSS)", postView.CommentFeed + "?format=ATOM");
81 101             base.AddGenericLink("application/rss+xml", "alternate", Server.HtmlEncode(Post.Title) + " (ATOM)", postView.CommentFeed + "?format=ATOM");
82 102  
83 103             if (BlogSettings.Instance.EnablePingBackReceive)
84 104                 Response.AppendHeader("x-pingback", "http://" + Request.Url.Authority + Utils.RelativeWebRoot + "pingback.axd");
85 105         }
86           }
87 106         else
88 107         {
89 108             Response.Redirect(Utils.RelativeWebRoot + "error404.aspx", true);
90 109         }
91 110     }
92 111  

You could stop there, the URLs will be right in the address bar, though not the links on the page. For those we need to tweak the RelativeLink property of the Post class in BlogEngine.Core\Post.cs and rebuild BlogEngine.Core.dll.

375 375         /// Only for in-site use.
376 376         /// </summary>
377 377         public string RelativeLink
378 378         {
379 379             get
380 380             {
381                   string slug = Utils.RemoveIllegalCharacters(Slug) + BlogSettings.Instance.FileExtension;
  381                 string slug = Utils.RemoveIllegalCharacters(Slug);// + BlogSettings.Instance.FileExtension;
  382                 string relRoot = "/";//Utils.RelativeWebRoot;
382 383  
383 384                 if (BlogSettings.Instance.TimeStampPostLinks)
384                       return Utils.RelativeWebRoot + "post/" + DateCreated.ToString("yyyy/MM/dd/", CultureInfo.InvariantCulture) + slug;
  385                     return relRoot + DateCreated.ToString("yyyy/MM/dd/", CultureInfo.InvariantCulture) + slug;
385 386  
386                   return Utils.RelativeWebRoot + "post/" + slug;
  387                 return relRoot + slug;
387 388             }
388 389         }
389 390  
390 391         /// <summary>
391 392         /// The absolute link to the post.
392 393         /// </summary>

If you're not one to tweak the source code you may download an otherwise vanilla 1.5 copy of BlogEngine.Core.dll from me. Throw that in the /bin folder on your site. Lastly you may wish to replace instances of <%=Utils.AbsoluteWebRoot %> in your Theme files with http://yoursite.com. I only had to change the one on the logo in site.master myself.

If you're thinking, "Hm, I should patch AbsoluteWebRoot and RelativeWebRoot in BlogEngine.Core/Utils.cs!" Knock yourself out and comment here when you've both changes working. (Unfortunately more is required, so I left them be.)

Summary

Comments

2010 April 13. 12:04 AM #

Unfortunately this setup will cause issues with your sitemap, which is highly important for SEO. Other exceptions in the redirect/rewrite rules may be necessary as well. I will eventually post the workaround for the sitemap, as well as my own exception list. (Maybe if I upgrade BE.NET.)

Christopher Galpin

Add comment

*
(private)

biuquote
  • Comment
  • Preview
Loading




Powered by my custom BlogEngine.NET. Content © 2012 Christopher S. Galpin