Brail's architecture
First, let's understand where Brail lives. Brail is a View Engine
for the Castle's
MonoRail web
development framework MonoRail is
MVC framework for
ASP.Net that allows true
Separation Of Concerns between your business logic and your UI code. Brail
come into play when it's time to write your UI code, the idea is that instead of
using a templating framework, like NVelocity
or StringTemplate, you can use a
bona fide programming language with all the benefits that this implies. The down
side of this approach is that programming languages usually has very strict
rules about how you can write code and that is usually the exact opposite of
what you want when you write a web page. You want a language that wouldn't get
in your way. This is where Brail come into play.
Brail is based on Boo, a
programming language for the .Net Framework which has a very unorthodox view of
the place of the user in the language design. Boo allows you to plug your own
steps into the compiler pipeline, rip out whatever you don't like, put in things
that you want, etc. This means that it pack a pretty punch when you needs it.
The options are nearly limitless. But enough raving about Boo, it's Brail that
you're interested in. What Brail does is to allow you to write your web pages in
Boo, in a very relaxed and free way. After you write the code, Brail takes over
and transform it to allow you to run this code. The Brail's syntax and options
is documented,
so I'll assume that you're already familiar with it.
Question: How Brail does all of it?
Answer: It's magic.
Pay no attention to the man behind the curtain:
So, what is really happening there? First we need to
understand what MonoRail is doing when it receives a request, here is the pretty
diagram:

Processing Requests:
MonoRail receive a request, calls the appropriate
controller and then calls to the view engine with the current context, the
controller and the view that needs to be displayed. Brail then takes over and
does the following:
-
Check if the controller has defined a layout. If it
has, pipe the view's output through the layout's output. (The view is
compiled the same way a view is)
-
Get the compiled version of a view script by:
- Checking if the script is already in cache. The cache is a hash
table ["Full file name of view" : compiled type of the view]
- If the script is already in the cache but the type is null this
means that the view has changed, so we compile just this script again.
- Instantiate the type and run the instance, which will send the
script output to the user.
A few things about changes in the views. Brail
currently allows instantaneous replacement of views, layout and common
scripts by watching over the Views directory and recompiling the file when
necessary, since this is a developer only feature, I'm not worrying too much
about efficiency / memory. I'm just invalidating the cache entry or
recompiles the common scripts. Be aware that making a change to the Common
Scripts would invalidate all the compiled views & layouts in memory
and they would all have to be compiled again. This is done since you can't
replace an assembly reference in memory.
The interesting stuff is most happening when Brail is
compiling a script. For reference, Brail usually will try to compile all the
scripts in a directory (but does not recurse to the child directories) in one
go, since this is more efficient in regard to speed / memory issues.
Occasionally it will compile a script by itself, usually when it has been
changed after its directory has been compiled or if the default configuration
has been changed. There isn't much difference between compiling a single file
and compiling a bunch of them, so I'm just going to ignore that right now and
concentrate on compiling a single script. Brail's scripts are actually a Boo
file that is being transformed by custom steps that plug into the Boo compiler.
Compiling Scripts:
Here is what happens when Brail needs to compile a
script:
-
Creating an instance of BooCompiler, and telling
if to compile to memory or to file (configuration option).
-
Adding a reference to the following assemblies:
Brail, Castle.MonoRail.Framework, the compiled Common Script assembly and
any assembly that the user referenced in the configuration file.
-
Run a very simple pre processor on the file, to
convert file with <% %> to a valid boo script.
-
Remove the default Boo's namespace (this is done
because common names such as list, date were introduced including the
default namespace and that meant that you couldn't use that as a parameter
to the view.
-
Replace any variable reference that has unknown
source with a call to GetParameter(variableName) which would use the
controller's PropertyBag to get it. GetParameter() throws if it can't find a
valid parameter, by the way. The reasoning is that this way you won't get
null reference exceptions if you are trying to do something like:
date.ToString("dd/mm/yyyy") and the controller didn't pass the date. Since
debugging scripts is a pain, this gives you a much clearer message.
-
Then the real transformation begins. Any Brail
script is turned into a subclass of the BrailBase class, which provides
basic services to the script and allow the engine to output the results to
the user. What is happening is that any "free" code, code that isn't in a
class / method is moved to a Run() method on the subclass. Any methods are
moved to the subclass, so they are accessible from the Run() method.
Anything else is simply compiled normally.
When Brail receive a request for a view it looks it up
as describe above (from cache/compiled, etc). A new instance of the view is
created and its Run() method is called. All the output from the script is sent
to the user (directly or via the layout wrapping it.)
BrailBase Class:
The BrailBase class has several useful method &
properties:
-
ChildOutput - Layouts are scripts that are using
their ChildOutput property to wrap their output around the child output.
This works as follows, a layout is created, and its ChildOutput is set to a
view's output, the view is then run. After the view run, the layout is run
and has access to the view's layout.
-
IsDefined(parameterName) - Check if a parameter
has been passed, this allows you to bypass GetParameter() throwing if
nothing has been passed.
-
OutputSubView() - Output another view.
To wrap it up:
As you can see, the man behind the curtain does quite
a bit, but it's really a lot of very small & simple steps. If you've any
questions, just email / IM me.