The conception, birth, and first steps of an application named Charlie

Subscribe: Atom or RSS

Implementing Raw Templating - Part 2

by Alister Jones (SomeNewKid)

In the previous weblog entry, I introduced the IWebTemplateParser interface, which looks like this:

public interface IWebTemplateParser
{
    String[] ControlDirectives { get; }
    String Parse(String template);
}

The first line allows each plugin to “tell” Charlie about any custom server controls that the plugin uses. But what is the purpose of the second line? Well, here is the next template that I inserted into the database:

<html>
    <head>
    </head>
    <body>
        <h1><insert:Text name="Welcome" /></h1>
    </body>
</html>

This is an example of the declarative templating system that I am now implementing. There are two things to note about the bold tag shown above.

First, using <insert:Text /> and <insert:Snippet /> and <insert:Image /> would require that each of these custom controls reside within the same assembly, since each prefix (being ‘insert’ in this case) can point only to a single assembly. However, while I want the website owner to have only one prefix to learn, I want each control to point to a different plugin and, therefore, a different assembly. So <insert:Text /> should go to the Globalization plugin, since that is where the text resources are handled. And <insert:Snippet /> should go to the HTML plugin, since that is where HTML snippets are handled. So I need some way of allowing the same prefix of ‘insert’ to point to different assemblies.

The second thing to notice about the above tag is that it contains no runat="server" parameter. That is a functional requirement of ASP.NET that should not be forced upon the website owner.

So, it is to solve the above two problems that the IWebTemplateParser has the Parse method. Before Charlie uses the template it draws out of the database, it will give the template to each plugin. Each plugin can then “fix” the template before it goes to the Page.ParseControl method. The Globalization plugin, for example, will look for the following tag:

<insert:Text name="Welcome" />

If found, the tag will be changed to this:

<globalization:Text name="Welcome" runat="server" />

With that simple replace operation, we have solved the two problems. The general ‘insert’ prefix has been replaced by a specific ‘globalization’ prefix. (Remember, the first line of the IWebTemplateParser interface is where each plugin can “tell” Charlie about custom prefixes.) We have also added the runat="server" attribute that ASP.NET requires.

Here is the full code from the Globalization plugin:

namespace Charlie.Globalization.Interface
{
   class TemplateParser : IWebTemplateParser
   {
      public String[] ControlDirectives
      {
         get
         {
            String[] directives = new String[1];
            directives[0] = 
             @"<%@ Register TagPrefix=""globalization""
                   Namespace=""Charlie.Globalization.Interface""
                   Assembly=""Charlie.Globalization"" %>";
            return directives;
         }
      }

      public String Parse(String template)
      {
         String from = @"<insert:Text(\s)";
         String to = @"<globalization:Text$1runat=""server"" ";
         template = Regex.Replace(template, from, to);
         return template;
      }
   }
}

Right now, the regular expression-based find-and-replace operation within the Parse method is overly simplistic. But right now I am just making sure the functionality works—the find-and-replace operations can be tweaked later. But the point is that the above code is very simple. Each plugin can inspect the template for known tags, such as <insert:Text>, and make any changes it requires. It will make changes so that a custom server control will be called into play. In this example, the Globalization plugin will use a custom server control that is an extension of the Literal control. Here is its code:

namespace Charlie.Globalization.Interface
{
   public class Text : Literal
   {
      public String Name
      {
         get
         {
            return this.name;
         }
         set
         {
            this.name = value;
         }
      }
      private String name;

      protected override void OnPreRender(EventArgs e)
      {
         this.Text = ResourceManager.Current.GetString(this.Name);
         base.OnPreRender(e);
      }
   }
}

This too is about as simple as a custom server control can get. So, what have we achieved here? Well, previously a template using localized text needed to be a User Control that included both a declarative server control and procedural code:

<%@ Control Language="C#" 
    Inherits="Charlie.Framework.Interface.TemplateControl" %>
<%@ Register 
    TagPrefix="aspx"
    Namespace="Charlie.Framework.Interface.Controls"
    Assembly="Charlie.Framework" %>
    
<h1><aspx:Label ID="Welcome" runat="server" /></h1>

<script runat="server">
    private void Page_Load(Object sender, EventArgs e)
    {
        Welcome.Text = ResourceManager.Current.GetString("Welcome");
    }
</script>

Now, the relative complexity above has been replaced with a very simple declarative tag:

<h1><insert:Text name="Welcome" /></h1>

That is all the code that is needed to insert localized text into the template. If the English version of the page is requested, the heading will be “Welcome”. If the French version of the page is requested, the heading will be “Bienvenue”. This is the sort of simplicity that I want from the “raw” templating system that I have devised. And so far, so good.

by Alister Jones | Next up: Charlie’s Own Two Feet

2 comments

______
Anonymous Anonymous said...  
 

Very Very nice :)

______
Anonymous Anonymous said...  
 

Hi

I have been looking at DDN recently and must say that I don't like it very much.

I am impressed with how quickly you can do things with it, but I just don't like the output at all and it does seem far too complicated as well.

Would it be possible for us to have a brief chat via email? If you wouldn't mind I'd just like to quickly discuss what your plans are etc.

I'd really appreciate it.


Thanks

Pete

peter(dot)morris[at]2ed.com

Post a Comment

----