Want to follow this site? Here's the RSS feed.

Fundamentals of Themelia - Page Aliasing

Thursday, June 12, 2008

This documentation has been updated for Themelia Framework 2.0 Beta 5.

One of the core HTTP handlers that ship with Themelia is the page alias handler, which allows users to access SEO-friendly (search engine optimization) paths. For example, a user would see /login/ instead of something disgusting like /Login.aspx.  Themelia page aliasing is similar to a concept called URL rewriting mechanism (though with major differences discussed further down).

Registering a Themelia page alias is done in two steps: first, register the path to a PageAlias handler and, second, register the alias with the alias configuration collection in Themelia, linking them together with a key.  The key is the key attribute on the alias and the text attribute or, if present, the referenceKey attribute on the handler declaration.  The key is very important.  While, as is explained in basic HTTP routing, handlers are loaded based on rules of specificity, aliases are loaded via the key.

Below is an example configuration snippet; notice that the HTTP handler name has an alias of "PageAlias", meaning it's registered in Themelia's internal HTTP handler factory.

<themelia.web>
  <webDomains>
    <add>
      <handlers>
        <add matchType="contains" name="PageAlias" text="/login/" />
        <add matchType="contains" name="PageAlias" text="/logout/" />
        <add matchType="contains" name="PageAlias" text="/contact/" referenceKey="c" />
      </handlers>
      <aliases>
        <!--login-->
        <add key="/login/" target="/Sequence_/Security.aspx" />
        <add key="/logout/" target="/Sequence_/Security.aspx" />
        <!--suggestion-->
        <add key="c" target="/Sequence_/Contact.aspx" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

Here you can see that the login and logout aliases are keyed to their respective handlers via the text while the contact alias is keyed via the referenceKey.  The referenceKey attribute is completely optional.  You may use it all of the time, some of the time, or none of the time.  However, as a common sense practice, you want to be congruent (not "consistent"; consistency says "I always drive 45mph.  Doesn't matter if it's a school zone or the interstate" while congruency says "I will drive according to the common sense underlying principles of my current situation: 18mph is a school zone and 65mph on the interstate.)  Therefore, you may want to either always use the referenceKey, or only use it when the handler text gets beyond 20 characters.

Now, by way of commentary, in the above example, when a user accesses either /login/ or /logout/, he or she will actually be sent /Page_/Security/Login.aspx under the covers. But what would differentiate between the two in /Security/Login.aspx? The answer is obvious: just check to see which the user is accessing. In the following example, you can also see how Themelia provides you with simple helper mechanisms:

public partial class Login : Sample.ThemeliaPage
{
    //- @Setting -//
    public class Setting
    {
        public const string IsAuthenticated = "IsAuthenticated";
    }

    //+
    //- #OnInit -//
    protected override void OnInit(EventArgs e)
    {
        if (Themelia.Web.Http.UrlPartArray.Contains("logout"))
        {
            Themelia.Web.HttpData.SetSessionItem<Boolean>(Setting.IsAuthenticated, false);
        }
        //+ check security
        if (Themelia.Web.HttpData.GetSessionItem<Boolean>(Setting.IsAuthenticated))
        {
            Themelia.Web.Http.Redirect("/");
        }
        //+
        base.OnInit(e);
    }
}

Themelia provides you with a class called Themelia.Web.Http which contains various members to aide development, including a UrlPartArray member which is an array containing the different parts of the virtual path accessed. In this case UrlPartArray[0] would have "Login" or "Logout". C# 3.0 LINQ is being used to check for this. Themelia.Web.HttpData also includes a number of generic methods to help you access namespaced (scoped) session, cache, and context items in a strongly typed manner.

Regular Expression Match and Variable Capture

One important thing you can do with page aliasing is use a regular expression to create patterns of matching.  You use a typical regular expression matching pattern with a $x replacement.

You need to be careful using this, however, as over use will create sloppiness.  You don't want to map all of one logical path to all of a physical path as this brings you right back to the tight coupling between the logical and physical structure.  For example, it's a very bad idea to use /product/([a-z]+)/$ to map to "/Page_/Product/$1.aspx.  That does absolutely nothing more than reconnect your logical and physical structures.  The point is to separate them, not to create a pattern that connects them just as tight as when you started.

Therefore, in order to create a clean, loosely-coupled structure, regular expressions should be used primarily for variable capture.  Here's an example:

<themelia.web>
  <webDomains>
    <add defaultPage="/Page_/Security/Home.aspx">
      <handlers>
        <add matchType="contains" name="PageAlias" text="/problem/([\-a-z0-9]+)" referenceKey="problem" />
      </handlers>
      <aliases>
        <add key="problem" target="/Page_/Quality/Problem.aspx?guid=$1" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

Notice a few thing about this. First, the regular expression in the handler text copies a variable to the $ variable in the alias.  If there were more than one capture, the number in the alias increments (i.e. $1, $2, $3, etc...)  Second, while you may still use the handler text as the key, regular expressions can be long and complex, therefore, it's probably a good idea to use the referenceKey for this.

One critical point about Themelias aliasing is that it's not technically "rewriting".  Rewriting is an old concept which actually changes the user's request.  So if /problem/([a-z]+) is rewritten to /Problem.aspx?guid=$1, then when /problem/asdf is accessed, HttpContext.Current.Request.Url.AbsoluteUri will show /Problem.aspx?guid=asdf.  Not very helpful.  Therefore, Themelia aliasing thus not rewriting anything, but just mapping a logical path to a physical one, allow the absolute URI to remain as /problem/asdf.

Now since we are talking about variable capture, the data must be captured somewhere.  Given the fact that the above looks like a query string you would think that you would have to access the data as a query string.  However, if that were the case, then you wouldn't actually be able to use a query string (i.e. /problem/asdf?hello=qwer).  Therefore, Themelia provides capture state accessible via Themelia.Web.HttpData.

You may access a single captured item via Themelia.Web.HttpData.GetCaptureItem or you may obtain all captured items via Themelia.Web.HttpData.GetCaptureItemMap( ).

Here's an example of getting the data The following example demonstrates this:

//- $OnSubmit -//
private void OnSubmit(Object sender, EventArgs e)
{
    if (Http.GetUrlPart(Http.Position.Penultima) == "problem")
    {
        //+ in this case, you could get this from Http.GetUrlPart(Http.Position.Ultima) in
        String guid = HttpData.GetCaptureItem("guid");
        Sample.Web.ExceptionSaver.Save(guid, txtDescription.Text);
    }
}

In a world of rewriting, your ability to use query string is just about gone.  Since /problem/adsf IS /Problem.aspx?guid=asdf, /problem/adsf?hello=qwer is very confusing to understand.  Therefore, in Themelia, you may still use a query string.  You may use Themelia.Web.HttpData.GetQueryItem to access a query item in the same place where you use GetCaptureItem(String item) to access a captured item.  Also, as you may have expected, you may use Themelia.Web.HttpData.GetQueryMap to access all query items.

For more information on Themelia HTTP data see the HttpData class documentation.

Initialization Parameters

You may also use Themelia initialization parameters with aliasing.  This essentially means that you can give handlers parameters that they may use for inline initialization.  The handlers have to be aware of the parameters (which is a more advanced topic), but the page alias handler is already initialization parameter aware.  Below is an example of using initialization parameters with the page alias handler.

<handlers>
  <add matchType="contains" name="PageAlias" text="/problem/([\-a-z0-9]+)">
    <parameters>
      <add name="target" value="/Page_/Quality/Problem.aspx?guid=$1" />
    </parameters>
  </add>
</handlers>

The parameters here are the exact same parameters you would set in the <aliases> section.  If you are doing a series of aliases, perhaps you would rather keep them in the <aliases> section, however, if you are only doing one perhaps initialization parameters are more up your alley.

Page Alias Fall Through Processor

(This section requires knowledge of Themelia fall through processors.)

In some situations, perhaps you have an entire web domain or perhaps an entire web site that is full of page aliases and you don't feel like declaring each one of them as handlers.  In this situation, you can use the PageAliasFallThroughProcessor.  The following configuration example demonstrates how to enable this type of scenario:

<themelia.web>
  <webDomains>
    <add>
      <handlers>
        <add matchType="pathStartsWith" name="Authentication" text="/authenticate/" />
      </handlers>
      <aliases>
        <!--login-->
        <add key="/login/" target="/Sequence_/Security.aspx" />
        <add key="/logout/" target="/Sequence_/Security.aspx" />
        <!--suggestion-->
        <add key="/contact/" target="/Sequence_/Contact.aspx" />
      </aliases>
      <processors>
        <add type="PageAliasFallThroughProcessor" />
      </processors>
    </add>
  </webDomains>
</themelia.web>

Remember, fall through processors only run if there if there isn't an explicit route for your current path.  Therefore, the /authentication/ path will still go to the Authentication HTTP handler, while everything else will go to the PageAliasFallThroughProcessor.

With these simple page aliasing features, you can kill off all those nasty ".aspx" files off all your pages. That one simple fix will make your entire web site look much more professional and make it much easier for users to remember your links. It also provides a nicely structured web site without a splurge of superfluous physical folders and default documents (i.e. default.aspx) laying all over the place, which, in turn, gives you a more centralized file management experience.

Links

Creative Commons License
This work is licensed under a Creative Commons Attribution 2.5 License.

Powered by the Minima 3.1 and Squid Micro-Blogging Library.

Built on Themelia Framework 2.0

Developed using NetFXHarmonics DevServer 1.1.

(all of which are NetFXHarmonics products created by David Betz)

Mini-icons are part of the Silk Icons set of icons at famfamfam.com