Introduction
Bellevue is a prototype of a new Open source template engine for ASP.NET MVC Framework. Bellevue uses pure HTML in its core - without any additional tags or syntax. It then uses CSS-like syntax to inject the active logic into the HTML - or in other words: to bind the data from the model to the view.
In this first part, I am covering the basics:
- Design goals
- The idea in brief
- Roadmap
- Getting Started in Visual Studio
- Declarations
- Relative positioning
- Functions
- Debugger
The second part is here and it goes into more advanced features and discusses the design and the future features I am planning.
Design Goals
The design goals of this new Template technology / View Engine are:
- Respect HTML as a first-class language as opposed to treating it as "just text".
- Don't mess with my HTML! The data binding code (Bellevue code) should be separate from HTML.
- Enforce strict Model-View separation as described by Terence Parr here.
The Idea in Brief
Pretty much all existing template engines (MVC view engines) start from HTML, but then force the developers to modify this HTML to insert active content in the middle of the HTML.
For example in ASP.NET Web Forms, you add <% ... %>
tags in places where you want the active content to be shown:
These modifications render the original HTML useless for many purposes: it can no longer be shown stand-alone in a web browser and most of the WYSIWYG editors can no longer show it properly.
Bellevue takes a different approach. It keeps the original structure of the HTML as-is:
... and then uses a separate CSS-like script to inject the active content within the HTML:
Bellevue View Engine is a fully ASP.NET MVC compatible view engine: It will support all features of ASP.NET MVC and you can mix it with Web Forms views and even with other view engines. It will be released as open source using some License, which I have not yet decided. I reserve the moral right to release additional tools or versions with commercial license, but the basic view engine will be free.
Roadmap
Before we get deeper into code, let's get the expectations straight: This is a preview demonstrating the idea. Even though I am releasing the source code, the point of this article is to get some feedback about the idea - not really about the implementation.
If you are looking for something you can actually use in production, please check back in one or two months. I am about to start a major rewrite to fix the hack-level code and I should hopefully have a more production-level implementation in May-June timeframe.
With that disclaimer, let's dig into the code...
Getting Started in Visual Studio
All the following code is in the Ope.Bellevue.DemoWeb
project in the downloads package.
To get started with Bellevue in a new Visual Studio ASP.NET MVC project, you need to first make the reference to "Ope.Bellevue.dll". Then, you need to add to your "Global.asax.cs" the registration of Bellevue view engine:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Add(new Ope.Bellevue.BellevueViewEngine());
}
Now you are ready to add a Bellevue view. View is just a HTML-file with .htm or .html suffix. The example here is "/Views/Demo/Demo1_Intro_Bellevue.html", which has the following HTML in the upper part of the body:
<div id="menucontainer">
<h1>Info about <b>Product 1</b></h1>
</div>
To change the "Product 1
" text to ViewData["Name"]
coming from the controller, we would add the following Bellevue script in the head
part of the HTML file:
<script type="text/bellevue">
h1 b { text: data(Name); }
</script>
Let's go through this: The script
tag is just like a normal script
tag according to HTML standard. Instead of type "text/JavaScript
", we are using type "text/bellevue
". This is a marker for the Bellevue parser to take the contents and apply that script to the HTML document on the server side. If the page is loaded without going through Bellevue view engine (e.g. directly from file system to browser), the browser will simply ignore a script
where it does not recognize the type.
The syntax of the script
is just like CSS: we have the selector "h1 b
", which will select our target element and apply the declaration "text: data(Name)
" to that element. Declaration "text
" tells the system to replace the inner text with the value which in this case is function "data(Name)
". This will return either ViewData["Name"]
or ViewData.Model.Name
.
The script
can of course also be an include
file like in "Demo2_Intro_Bellevue.html":
<script type="text/bellevue" src="Demo2_Intro_Bellevue.bvue"></script>
The include
files must have the .bvue suffix, but the suffix can be left out from the src
attribute. Include files are searched for like partial views, so they can also be in the Shared folder.
The selectors can be much more complex like in "Demo2_Intro_Bellevue.bvue".
#main table tr:nth-child(1) td:nth-child(2) { text: data(Name); }
I am currently using the Fizzler CSS selector with HTML Agility Pack and the CSS parser is the one from BoneSoft here in code project. They all are great projects, but they have some limitations and bugs, so you do not get all the features of CSS Level 3 selectors. But a powerful set in any case.
Declarations
Just replacing text would not be that great, but of course there are also other declarations. This preview version has the following:
text
: As above, note that the text is HTML-encoded
attr
: Replaces the value of an attribute, attribute encoded
html
: Replaces the inner HTML, not encoded
text-format
: Applies given string
formatting to an object
text-replace
: Replaces specified text within the original inner text with a new text
checked
: Sets the checked attribute of an input
style
: Sets the style attribute
class
: Sets the class attribute
value
: Sets the value attribute
apply-template
: See templates below
render-control
: Renders a partial view
- Can be a Bellevue partial view or ascx (or from any other properly implemented ASP.NET view engine)
action-link
: The same as HtmlHelper.ActionLink()
in Web Forms
- This is a proto on how HTML helper methods can be used through Bellevue
- I am planning to implement other helper methods too, but I need to go through all those overloads and think how it all makes sense.
To get more details on how you can use these declarations, see the "Declarations.html" view. There are some examples.
Relative Positioning
CSS selector will always match an individual element. By default, most declarations target the inner nodes (or inner HTML) of the element that was matched by the selector, i.e. p { text: "foo" }
will replace the HTML inside <p>any html</p>
to be <p>foo</p>
.
Sometimes this is not quite enough, but you can get more control by using standard suffixes of declaration names:
-outer
: Replaces the outer HTML of the element
-add
: Appends a new node inside the element
-insert
: Inserts a new node as the first child node in the element
-before
: Inserts a new node just before the element
-after
: Inserts a new node just after the element
-inner
: Explicitly states to target the inner HTML. Added in case some declarations would not target inner HTML by default. May be removed in later versions.
For more information and examples, see "Demo5_RelativePosition.html" in the demo project.
Functions
We already used the "data()
" function above, but there are of course others. The current implementation has the following set:
first(collection)
: The first or only item in the collection
last(collection)
: The last or only item in the collection
rest(collection)
: All, but the first item in the collection
trunc(collection)
: All, but the last item in the collection
strip(collection)
: The collection without null
values
length(collection)
: The length of the collection
join(collection, separator)
: Concatenates all items in the collection
item(property path)
: Data object from the current item passed to a template
index(base)
: Zero-based index number of the current item adding the base number
get(container, property path)
: A property from any object
data(property path)
: Data from the ViewData
object
model(property path)
: Gets data from the ViewData.Model
object
concat(value1, value2, valueN)
: Concatenates string
values
if(condition, value if true, value if false)
: Like ternary operator ("?
") in C#
isnull(value, value if null)
: Like in SQL
false()
: Boolean value false
null()
: Null
value
true()
: Boolean value true
You can nest functions and much of them are the same as in StringTemplate. Most of the functions are probably obvious, but there is a reference in here - and it is also included in the demo Project.
Debugger
If you have checked any of the examples in the demo project, you have no doubt noticed that at the bottom of each page, there is the "Bellevue debugger". The debugger shows you possible warnings and errors, rule sets that are applied to elements and the contents of the model (limited implementation) and ViewData
dictionary.
The debugger is now on by default, but if it bothers you, you can set it off by changing the Global.asax.cs:
var engine = new Ope.Bellevue.BellevueViewEngine();
engine.ShowDebugger = false;
ViewEngines.Engines.Add(engine);
This is of course just temporary implementation. In the final version, you should have much more control over the debugger, but at this stage you usually want to see the debug information.
Wait, There Is More...
So these are the basics. If this made you interested, Part 2 takes you through Templates and Master pages. The demos are already in this download project. I will also give some more information about the ideas for future development and talk about the design and implementation.