Brail documentation
Brail is a view engine for MonoRail which allow you to use the same framework (and language) on both ends of the application. You write the business logic using Boo (or any other .Net language), and then you write the user interface using Boo. No need for a mental switch or learn another "easy" templating language. I'm going to assume that you are at least somewhat familiar with MonoRail and Boo, to make this a little easier.
Depracation Alert
This documentation refers to an old version of Brail. You can get the latest documentation at Castle Website
How to use?
Add the following files to your bin directory:
- Brail.dll
- Boo.Lang.dll
- Boo.Lang.Compiler.dll
- Boo.Lang.Parser.dll
Edit your web.config file to include the following line (in the monoRail config section, of course):
<viewEngine
viewPathRoot="Views"
customEngine="Brail.BooViewEngine, Brail" />
And that is is, you are now capable of using Boo scripts to write views in MonoRail! Now that you can use it, let's see what you can do with it.
Before we get to how to use it, I must send huge thanks to the Boo Community, for being so helpful!
The mandatory hello world example:
First, Brail scripts are not normal Boo programs, they require that you would have at least one statement at the global namespace, which is what MonoRail will end up calling when your view is invoked. Assuming that you've already have a controller, and that you've setup your environment correctly, you can simply do this:
Hello, World!
Brail will take this script, compile it and send its output to your browser. The nice thing about it is that if you would change it to say:
Hi, World!
You will instantly get a the updated view, this is highly important to development scenario (and wasn't that easy to implement, btw. :-) )
Note: how does it work behind the scenes?
The script is loaded (usually batch loaded, actually) and compiled. There is some magic there that send anything not wrapped in <% %> directly to the user (which will also expand ${} expression to their values). The code is compiled and cached, so you pay the compilation price once.
A complete example:
<html>
<head>
<title>${title}</title>
</head>
<body>
<p>The following items are in the list:</p>
<ul>
<%
for element in list:
output "<li>${element}</li>"
%>
</ul>
<p>I hope that you would like Brail</p>
</body>
</html>
The output of this program (assuming list is (1,2,3) and title is "Demo" ) would be:
<html>
<head>
<title>Demo</title>
</head>
<body>
<p>The following items are in the list:</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
</html>
And the rendered HTML will look like this:
The following items are in the list:
Output methods:
Since most of the time you will want to slice and dice text to serve the client, you need some special tools to aid you in doing this. Output methods* are methods that are decorated by [Html] / [Raw] / [MarkDown] attributes. An output method return value is transformed according to the specified attribute that has been set on it, for instance, consider the [Html] attribute:
<%
[Html]
def HtmlMethod():
return "Some text that will be <html> encoded"
%>
${HtmlMethod()}
The output of the above script would be:
Some text that will be <html> encoded
The output of a method with [Raw] attribute is the same as it would've without it (it's supplied as a NullObject for the common case) but the output of the MarkDown attribute is pretty interesting. Here is the code:
<%
[MarkDown]
def MarkDownOutput():
return "[Ayende Rahien](http://www.ayende.com/), __Rahien__."
%>
${MarkDownOutput()}
And here is the output:
<p><a href="http://www.ayende.com/">Ayende Rahien</a>, <strong>Rahien</strong>.</p>
Markdown is a very interesting and I suggest you take a look at i than they need.
* Temporary name
Using variables:
A controller can send the view variables, and the Boo script can reference them very easily:
My name is ${name}
<ul>
<%
for element in list:
output "<li>${element}</li>"
%>
</ul>
Brail has all the normal control statements of Boo, which allows for very easy way to handle such things as :
<% output AdminMenu(user) if user.IsAdministrator %>
As you can guess, this will send the menu to the user only if he is administrator :-)
One thing to note about these var/onethignail is taking the variable and trying to find a matching variable in the property bag that the controller is passing. If the variable does not exist, this will cause an error, so pay attention to that. You can test that a variable exists by calling the IsDefined() method.
<%
if IsDefined("error"):
output error
%>
Using sub views:
There are many reasons that you may want to use a sub view in your views and there are several ways to do that in Brail. The first one is to simply use the common functionality. This gives a good solution in most cases (see below for a more detailed discussion of common scripts).
The other ways is to use a true sub view, in Brail, you do that using the OutputSubView() method:
Some html
${OutputSubView("/home/menu")}
some more html
You need to pay attention to two things here:
- The rules for finding the sub view are as followed:
- If the sub view start with a '/', then the sub view is found using the same algorithm you use for RenderView()
- If the sub view doesn't start with a '/', the sub view is searched starting from the current script directory.
- A sub view inherit all the properties from its parent view, so you have access to anything that you want there.
Passing parameters to sub views:
You can also call a sub view with parameters, like you would call a function, you do it like this:
${OutputSubView("/home/menu", { "var": value, "second_var": another_value } ) }
Pay attention to the brackets, what we have here is a dictionary that is passed to the /home/menu view. From the sub view itself, you can just access the variables normally. This variables, however, are not inherited from views to sub views.
Including files:
Occasionally a need will arise to include a file "as-is" in the output, this may be a script file, or a common html code, and the point is not to interpret it, but simply send it to the user. In order to do that, you simply need to do this:
${Boo.Lang.Useful.IO.TextFile.ReadFile("some-file.js")}
Of course, this is quite a bit to write, so you can just put an import at the top of the file and then call the method without the namespace:
<%
import Boo.Lang.Useful.IO
%>
${TextFile.ReadFile("some-file.js")}
Principal of Least Surprise:
On general, since NVelocity is more or less the "official" view engine for now, I tried to copy as much behavior as possible from NVelocityViewEngine. If you've a question about how Brail works, you can usually rely on the NVelocity behavior. If you find something different, that is probably a bug, so tell me about it.
Common Functionality:
In many cases, you'll have common functionality that you'll want to share among all views. Just drop the file in the CommonScripts directory - (most often, this means that you will drop your common functionality to Views\\CommonScripts) - and they will be accessible to any script under the site.
Note:
The common scripts are normal Boo scripts and gets none of the special treatment that the view scripts gets.
An error in compiling one of the common scripts would cause the entire application to stop.
Here is an example of a script that sits on the CommonScripts and how to access it from a view:
Views\\CommonScripts\\SayHello.boo - The common script
def SayHello(name as string):
return "Hello, ${name}"
Views\\Home\\HelloFromCommon.boo - The view using the common functionality
<%
output SayHello("Ayende")
%>
http://yourhost/home/hellofromcommon.rails - The output from the script
Hello, Ayende
Layouts:
Using layouts is very easy, it's just a normal scripts, that outputs ChildOutput property somewhere, here is an example:
Header
${ChildOutput}
Footer
Referencing your business logic assemblies from the views:
Let's consider this code:
<%
for user in users:
output "<p>${user.Name} - ${user.Email}</p>"
%>
Looks simple, right? The problem is that Brail doesn't really knows what user is, so it uses reflection to get the values of the Name and Email properties. This is, of course, quite expensive in performance terms. What is the solution? You need to tell Brail to add a reference to the assembly where User is defined. You can do that by adding this line to your web.config file (see the full configuration section below for more details).
<Brail> <reference assembly="assembly.with.user.object"/> </Brail>
And then, in your view code, you write:
<%
import My.Models
%>
<!-- lots of html code ->
<%
for user as User in users:
output "<p>${user.Name} - ${user.Email}</p>"
%>
With this simple change, you've completely eliminated the use of reflection and probably increased by a fair margin your application performance.
Note:
This is a feature that was added solely for performance reasons. This actually turns Brails into a nearly feature complete classic ASP clone. Please do not write any logic that is not strictly a presentation logic into the views.
Method, Properties and Macros, oh my:
This is the short list of the available API from inside a view:
| Type |
Name |
Description |
Usage |
| Properties |
ChildOutput |
The output of a child view, which is only applicable for views that are used as layout. |
${ChildOutput}
- or -
<% output ChildOutput %>
|
| OutputStream |
The output stream that is used to send data to the client. |
<% OutputSteam.Write("direct") %> |
| Methods: |
IsDefined( name as string ) |
Return true if a variable was defined. |
if IsDefined("error"): |
| |
GetParameter( name as string ) |
Get the value of the specified parameter. Will throw if the parameter is not found. This is the method that is called whenever you call an unknown variable. |
GetParameter("ajaxHelper").LinkTo( ... )
- or -
ajaxHelper.LinkTo( ... )
|
| OutputSubView( subViewName as string) |
Sends the output of the specified sub view to the client. |
OutputSubView("/SubViews/Menu") |
| Fields: |
context |
The IRailsEngineContext for the current request |
${context.LastException} |
| |
controller |
The controller for this request |
${controller.GetType().Name}.${controller.AreaNAme}.${controller.Acrion} |
| properties |
The hash table containing all the parameters for this request. |
if parameters["error"] is not null: |
| viewEngine |
The view engine instance that is used for this request. |
|
| Macros: |
output |
Sends whatever its argument is to the client |
output "Hello, world!" |
Configuring Brail:
The default configuration should suffice for most cases, but if you want to change the configuration, you can do so by adding a configuration section handler to the web.config file:
<configSections>
<section name="Brail"
type="Brail.BrailConfigurationSection, Brail" />
</configSections>
Here is the default configuration for Brail:
<Brail
debug="false"
saveToDisk="false"
saveDirectory="Brail_Generated_Code"
batch="true"
commonScriptsDirectory="CommonScripts">
<reference assembly="My.Assembly.Name"/>
</Brail>
| Option: |
Default Value: |
Possible values: |
| Debug |
Generate debug or retail code |
false |
true - generate debug code false - generate retial code |
| saveToDisk |
Save the generated assemblies to disk - useful if you want to know what is going on behind the scenes. |
false |
true - save assemblies to disk false - use entirely in memory |
| saveDirectory |
The directory to save the generated assemblies to. The path can be relative or absolute, if relative, the default ASP.Net bin path will be used. If the directory does not exist, it will be created. |
"Brail_Generated_Code" |
Any valid path |
| batch |
Batch compilation, compile all the scripts in one folder to a single assembly. This does not work recursively. |
true |
true - all scripts in a folder will be compiled to a single assembly false - each script will be compiled to its own assembly |
| commonScriptsDirectory |
The directory where all the common scripts are. This can be a relative or absolute path, if relative, the Views directory of the application will be used as the base. If the directory does not exist, it will not be created. |
"CommonScripts" |
Any valid path |
| reference element, assembly attribute |
This tells Brails that all your scripts should reference the specified assembly(ies), this allows strong typing in the views and avoid the cost of reflection. |
none |
The assembly attribute must contains a valid assembly name that is reachable to the application by using Assembly.Load() (usually this means that it's located on the bin directory of the application. |
Errors:
Any error is the scripts (compilation errors, exception, etc) will be viewable for the local user only. A remote user will get the usual error page.
One thing to be aware of with batch compilation is that if one of your scripts has an error, it will cause the entire batch to fail, and each script in the directory will have to first try the batch option, and when that fail, compile itself as a stand-alone assembly. This can be bad for performance if there are a lot of scripts in a directory. However, a second request for such a script would be served from memory, so it's not too bad.
Debugging:
While it should be possible to debug the views scripts (add System.Diagnostics.Debugger.Break() instead of a breakpoint), I don't recommend it. There is a quite a bit of compiler magic behind Boo as it is, and Brail does it fair share as well. It's likely that you won't get a good experience out of it.
Performance:
I've not tested it with any serious load, but it should be good enough for everything you would throw on it. If you want to use Brail for a 10 million transactions a day, I would suggest measuring first, though. :-)
Batch compilation should reduce compile time and memory size. The code is not interpreted, it's statically compiled (very similar to how ASP.Net does it) and run whenever a request come in. Currently there is not further reason to complicate the code until someone would actually need it. The second time that a request comes in for a page, it's already compiled and can immediately serve the request.
A change in a single file will cause a separate assembly to be loaded, and all future requests will go the the new assembly immediately. Be aware that a large number of changes in an application can cause an assembly leak, since the assemblies cannot be unloaded until the AppDomain is unloaded. This isn't a problem in production scenarios, and on a development machine, the usual IIS application resets should take care of it.
If you think that reflection kills your performance, make sure to reference your relevant assemblies and use casting to the appropriate types when applicable.
The Small Print:
Multi-threading: Brail should be thread safe, I say should because it's hard to be 100% of such a thing. But I believe is that the worst case scenario is a duplicate assembly load, but will have no affect on the application itself. I would be quite surprised if this ever happen, though.
Boo Magic: The boo compiler allows you to insert your code in the middle, and do arbitrary transforms on the code. This is what allows a lot of the magic that is going on in Brail. I couldn't have done it without the coolness already embedded in Boo.
Limitations:
Currently I'm not aware of any limitations regarding the implementation :-), I'm sure they will come up eventually.