CAPTCHA Time

By
Dave
2010
Jul
22
23:10
Posted in

It's taken a few weeks for the spam-bots to notice my blog engine, but they're now starting to become more active. There are many options in the toolbox for dealing with spam comments, one of which is to add a CAPTCHA. This is a test in which a computer generates a challenge, and then attempts to verify that the response is generated by a human.

There are many CAPTCHA implementations available (reCAPTCHA is a good example), however I though it would be instructive to generate my own. The System.Drawing.Drawing2D namespace got me started, as shown below in Figure 1.

CAPTCHA image

Figure1. Initial CAPTCHA image (enlarged)

The image is generated by a controller action, returning a FileResult object with the appropriate content type. A new image is generated after a configurable interval. There are numerous ways to implement the CAPTCHA check when the comment form is submitted, the simplest of which is to have stored the CAPTCHA data in ASP.NET session state. Given this project is intended as a lightweight, personal blogging engine and that I wouldn't envisage scaling out across multiple servers, this doesn't seem a bad approach for the time-being.

Of course, it's rather sad that I have to use a CAPTCHA, and I apologise for inconveniencing anyone submitting a valid comment.

SEO Results

By
Dave
2010
Jul
19
16:16
Posted in

Shortly after implementing the changes to my blog engine as described in the post on Search Engine Optimation, my blog came up as the top result in a Bing search for "Dr Dave", as shown below in Figure 1. Possibly coincidence, but a good outcome nonetheless.

Bing search for Dr Dave

Figure 1. Top search result in Bing!

Still got some way to with Google however, as the same search came 8th overall...

Control Integration

By
Dave
2010
Jul
19
11:13
Posted in

There's still some tweaking to do on my radial controls discussed in Part 1 and Part 2 of a previous post, but I wanted to integrate them into the project. I've included a couple of screenshots below. Note that the size of the control has been enlarged for clarity and that the menu structure is purely for test-purposes.

Radial control for boolean settings

Figure 1. Radial control for boolean settings.

Radial control for numeric settings

Figure 2. Radial control for numeric settings.

I decided to render all of the "focus arcs" around the edge of the control, highlighting the relevant arc as appropriate, so that the pointer for numeric values didn't "float" in space when it wasn't adjacent to the currently focussed segment.

The control allows me to define a hierarchical menu structure and update properties using reflection, so it's trivial to add another entry. I can also define min/max ranges, format strings, rates of change when using the keyboard etc. For example, the menu item for "zoom" shown in Figure 2 looks like this:

new MenuItem
{
    FormatString = "Zoom\n{0}",
    PropertyInfo = typeof(Camera).GetProperty("Zoom"),
    Min = 0.0f,
    Max = 45.0f,
    Rate = 1.0f,
    TickFormatString = "0.0",
    ValueFormatString = "0.00",
}

Search Engine Optimisation

By
Dave
2010
Jul
16
17:16
Posted in

Search Engine Optimisation (SEO) is an important consideration for any website. In addition to the sitemap already discussed for my blog engine, I'd been meaning to do some further work on SEO and I thought I'd start by downloading the SEO Toolkit and throwing it at my site.

Fortunately, it picked up all sorts of "violations". In order to correct these, and other issues relevant to SEO, I made the following changes:

  1. Corrected HTML formatting issues such as non-closed or improperly-nested tags
  2. Added <meta name="keywords"> and <meta name="description"> tags to the post detail view and post model (and updated my posts with relevant tags and descriptions)
  3. Removed multiple canonical formats when using paging-links. For example, instead of rendering a link to http://drdave.co.uk/blog/?page=1, I just use http://drdave.co.uk/blog/
  4. Issued HTTP 301 (Moved Permanently) status codes for links to my previous blog engine, as discussed in the comments on legacy & not-found links.

These are just some of the potential updates that may help optimise my blog engine for search engines, but they are a good starting point.

A Matter of Controls Part 2

By
Dave
2010
Jul
14
23:53
Posted in

In Part 1 I showed some screenshots of a radial dial for controlling settings. I've made the following visual changes:

  • Removed the central buttons and to allow a Surface tag to control the dial position and orientation
  • Adjusted the size and fonts to use a tag 39mm in diameter (a poker chip)
  • Added a label indicating current category above the dial which can also be used to drag the dial with the mouse
  • Added a back button to the last (clockwise) segment to navigate back to the previous category
  • Added a ring to the centre of the dial to indicate dial focus when more than one dial is present. The arc segments around the outside indicate item segment focus for a given dial
  • Added highlighting to show selected or pressed item states
Bool dial

Figure 1. Radial control for boolean settings

Blog Engine Download

By
Dave
2010
Jul
11
16:10
Posted in

I thought I'd make available the Blog Engine I've just written using ASP.NET MVC2. This is a lightweight, personal blogging engine, and is being used to run this site. It is intended for use with IIS7 (running in Integrated Mode). Currently it uses XML files for storage, and no databases are required.

The following downloads are available:

  1. Dr Dave's Blog Engine (binaries and ASP.NET pages) (zip'd), 188Kb. Files required to run Dr Dave's Blog Engine.
  2. Dr Dave's Blog Engine (source code) (zip'd), 47Kb. Visual Studio 2010 project for Dr Dave's Blog Engine

To get started with the binaries, follow these steps:

  1. Un-zip the blog engine binaries and ASP.NET pages.
  2. If your website is not on the root web, i.e. http://{domain}, but instead sits under a sub-directory, e.g. http://{domain}/{directory}, update the "VirtualPath" setting in the Web.Config file with the directory, e.g.

    <add key="virtualpath" value="/{directory}">

    Don't forget the forward-slash before (but not after) the directory name.

  3. Change the Title and Subtitle properties, also in Web.Config.
  4. If you would like to change the Author name, update the value in the /App_Data/users.xml file.
  5. Update the feed title and link properties in the site.master file, e.g.

    <link rel="alternate" type="application/rss+xml"
        title="My Title" href="http://{domain}/{directory}/feed" />
  6. Copy the files to your website.
  7. Navigate to http://{domain}/{directory}, and you should see the following:

    front page

    Figure 1. Front page after installation

  8. Click "Sign In". The default username is "admin" with a password of "password".
  9. Click on "Change Password" and change the password. A combination of upper-case letters, lower-case letters and numbers is recommended, with a length of at least six characters.

If you decide to use this engine to run your blog, please keep the "Powered by" link, and I'd be grateful if you could inlcude an acknowledgement.

This project remains under active development as and when bugs are identified, or further features are required and I have the time. Note that I do not have time to support the installation or operation of this blog engine, however I will endeavour to answer questions in comments.

Sitemaps

By
Dave
2010
Jul
10
17:19
Posted in

As part of optimising the results from search engines such as Bing and Google, I added a dynamic site map. The structure of this file is defined by sitemaps.org. In order to generate a suitable file, I added a simple action as shown below in Listing 1.

public ContentResult Sitemap()
{
    XNamespace xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
    XElement root = new XElement(xmlns + "urlset");

    // home
    root.Add(GetUrlElement(xmlns, ""));

    // posts, categories etc
    foreach (...)
    {
        string url = Url.RouteUrl("MyRoute", new { myparam = value });
        root.Add(GetUrlElement(xmlns, url);
    }

    return Content(root.ToString(), "text/xml", Encoding.UTF8);
}

private XElement GetUrlElement(XNamespace xmlns, string relativeUri)
{
    return new XElement(xmlns + "url",
        new XElement(xmlns + "loc", new Uri(Request.Url, relativeUri).AbsoluteUri));
}

Listing 1. Action to generate dynamic sitemap

While not shown above in Listing 1, I also added <lastmod /> elements, e.g. which corresponded to the last-modified date of a post. The final step was to add a suitable route to global.asax, as shown below in Listing 2.

routes.MapRoute(
    "Sitemap",
    "sitemap.xml",
    new { controller = "MyController", action = "Sitemap" }
);

Listing 2. Sitemap route

The sitemap is then available at http://{domain}/sitemap.xml and can be submitted to search engines using their webmaster tools. For Bing, the webmaster pages can be found at http://www.bing.com/webmaster. For Google the link is http://www.google.com/webmasters/tools.

Legacy & Not-Found Links

By
Dave
2010
Jul
10
17:06
Posted in

I needed to deal with existing links to my previous blog engine, as well as invalid requests to the new engine.

Legacy Links

As I mentioned previously, my new blog engine has opted for a different URL structure to my old engine. In order to support existing links to my old blog posts, I added some legacy routes to my routing table, as follows:

  1. Category links of the format http://{domain}/{directory}/category/{name}.aspx are mapped to my existing controller with the following route:
    routes.MapRoute(
        "MyRoute",
        "category/{name}.aspx",
        new { controller = "MyController", action = "MyAction" });
    
  2. Post links of the format http://{domain}/{directory}/post/{name}.aspx are mapped to my existing controller with the following route:
    routes.MapRoute(
        "MyRoute",
        "post/{name}.aspx",
        new { controller = "MyController", action = "MyAction" });
    
  3. Month links of the format http://{domain}/{directory}/{year}/{month}/default.aspx are mapped to my existing controller with the following route:
    routes.MapRoute(
        "MyRoute",
        "{year}/{month}/default.aspx",
        new { controller = "MyController", action = "MyAction" });
    
  4. The syndication link http://{domain}/{directory}/syndication.axd?format={format} is mapped to my existing controller with the following route:
    routes.MapRoute(
        "MyRoute",
        "syndication.axd",
        new { controller = "MyController", action = "MyAction" });
    

The nice thing about this approach is that I can support these legacy links without any additional code, just these additions to my routing table.

Not-Found Links

Requests to the new blog engine might not return anything for a couple of reasons:

  1. An invalid controller or action is specified
  2. A invalid parameter is passed to a controller action

For invalid controller requests, a catch-all route is a good starting point, such as the following:

routes.MapRoute(
    "Error",
    "{*url}",
    new { controller = "Error", action = "Http404" });

This ensures that requests of an unrecognised pattern will be passed to an error controller, and an appropriate view used to render an error.

For invalid parameters, additional code is required in the relevant controllers to redirect to a "Not Found" view, for example when a requested post is not found.