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

Subscribe: Atom or RSS

Implementing Raw Templating - Part 1

by Alister Jones (SomeNewKid)

Over the last few weblog entries, I have described the “raw” templating system that I will add to Charlie.

In describing it as a flexible templating system, I noted that I would use a default-and-specific approach to templating. A given type of webpage (such as an Article or a Photo Gallery) would have a default template, but a specific page can override that default template and effectively “tweak” it for just that specific page. This is the same cascading logic that I used for the authorization roles in Charlie’s security system. So the default-and-specific approach to templating was implemented easily, using the same database schema and the same sort of code.

Into the database I inserted the following test template:

<html>
    <head>
    </head>
    <body>
        <h1><asp:Label text="Welcome" runat="server" /></h1>
    </body>
</html>

The code to get this template onto the Page surface is incredibly simple. The code here executes within a code-behind or code-beside file (that is, within a class that inherits from System.Web.UI.Page).

String template = this.Template.Text;
Control parsed = this.ParseControl(template);
this.Controls.Add(parsed);

The above code will take the raw template string, parse it to end up with a handful of web server controls, and place those controls onto the Page surface. That part was easy, since the template above used built-in web server controls. The next part was to introduce custom server controls. Here is the next template I attempted to use:

<html>
    <head>
    </head>
    <body>
        <h1><custom:Label text="Welcome" runat="server" /></h1>
    </body>
</html>

The custom server control will give rise to the following exception:

Unknown server tag 'custom:Label'.

Normally, the .aspx page will have the following directive to “tell” it about custom controls:

<%@ Register 
    TagPrefix="custom" 
    Namespace="Charlie.Framework.Interface.Controls" 
    Assembly="Charlie.Framework" %>

However, even if that directive were to exist on the Page, such directives will not apply to any string of controls given to its ParseControl method. I had actually faced this same problem long ago when I was new to ASP.NET, and I posed this problem on the ASP.NET Forums. Fortunatey, Teemu Keiski gave me the answer. If the string that is passed into ParseControl contains custom tags, you must also pass in the directives. So, the string that is given to the ParseControl method must look like this:

<%@ Register 
    TagPrefix="custom" 
    Namespace="Charlie.Framework.Interface.Controls" 
    Assembly="Charlie.Framework" %>
<html>
    <head>
    </head>
    <body>
        <h1><custom:Label text="Welcome" runat="server" /></h1>
    </body>
</html>

The above will work, and the custom server control will be added to the Page. The remaining problem, however, is that Charlie cannot possibly know all of the directives that may be needed, since only the plugins will know. This is an easy problem to solve, since we have an existing IPlugin interface that defines those methods and properties that a plugin must implement in order for Charlie to “query” the plugin. The updated IPlugin interface looks like this:

public interface IPlugin
{
    Int32 ID { get; }
    String Name { get; }
    IWebModule[] GetModules();
    IWebResponseFilter[] GetResponseFilters();
    IWebContextFilter GetWebContextFilter();
    Presenter[] GetPresenters();
    IWebTemplateParser GetTemplateParser();
}

The last line is the new line. This means that Charlie can get the information it needs from the plugin in order to use custom server controls in the template strings that get passed to ParseControl. Here is what the IWebTemplateParser interface looks like:

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

The first line of the interface allows the plugin to “give” to Charlie an array of strings, each of which will be a directive that needs to be added to a template before it is given to the ParseControl method. Here is a concrete example:

public String[] ControlDirectives
{
    get
    {
        String[] directives = new String[1];
        directives[0] = 
          @"<%@ Register 
                TagPrefix=""custom"" 
                Namespace=""Charlie.Framework.Interface.Controls""
                Assembly=""Charlie.Framework"" %>";
        return directives;
    }
}

And with this interface, the plugins can use any custom server controls that they want, and Charlie never needs to know about them. All that happens is that Charlie grabs the text of the template, and then says to each plugin, “If you use custom server controls, give me their directives.” Charlie then adds the directives to the front of the template, before passing it to the ParseControl method.

String template = this.Template.Text;
StringBuilder builder = new StringBuilder();
foreach (IWebTemplateParser parser in 
         PluginManager.Current.GetTemplateParsers())
{
    if (parser.ControlDirectives != null)
    {
        foreach (String directive in parser.ControlDirectives)
        {
            builder.Append(directive);
        }
    }
}
String directives = builder.ToString();
Control parsed = this.ParseControl(directives + template);
this.Controls.Add(parsed);

So that was the first problem to be solved in implementing the raw templating system: allowing for custom server controls. In the next weblog entry, I will explain the second part of the IWebTemplateParser interface.

by Alister Jones | Next up: Implementing Raw Templating - Part 2

0 comments

----