Developer Review - Four ASP.NET MVC View Engines
- Posted: Oct 04, 2010 at 6:00 AM
- 26,676 Views
- 6 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
In this Developer Review, we evaluate four view engines available for use with ASP.NET MVC. First, we discuss the role a view engine plays in a website built with ASP.NET MVC, then we provide details about the four view engines in order to help you decide which one suits your needs.
|
Jason Haley |
Source Code: Download in Branches/Playground directory
|
When we refer to a view engine in ASP.NET MVC, we are talking about three pieces of functionality:
· A template locator/provider (implementation of IViewEngine)
· A template that can render itself (implementation of IView)
· A template engine that can parse and compile the view file syntax into executable code
Combining these three pieces, a view engine provides your controllers with the ability to translate views into Html.
Though the view engine that ships with ASP.NET MVC has all the functionality you need to create views for just about any website project, it is also easily replaced.
As with choosing a programming language in .Net, there are many reasons why you may choose one view engine over another:
Syntax
ASP.NET MVC’s default view syntax is sometimes described as “tag soup.” This is due to the mix of all the
<% … %> tags along with the Html markup and causes some developers to feel like the view is messy.
Skillset/background
Many developers have already used the Castle Monorail MVC framework and are experiences with the view engines provided for that framework. Accordingly, it makes sense to leverage that experience by using a similar view engine in ASP.NET MVC.
Productivity
For you, productivity may mean quick and easy to learn syntax or it may mean terse and less code. Depending on which is right for you, you may choose a different view engine.
Specialized Problem Domains
Some view engines use a DSL-like-language to represent the view, which may fit well within your environment.
Testability
View engines that don’t depend on the System.Web.UI.Page are easier to test than the default ASP.NET MVC view engine, which uses Page for a base class of its view implementation.
Division of labor
If you have designers working on the views, or just want minimal logic in your views, you should choose a view engine that easily mixes with Html.
|
View Language Features |
||||
|
Feature |
WebFormViewEngine |
Spark |
NHaml |
Razor |
|
Multi Line Code Blocks |
X |
X |
X |
X |
|
Inline Expression |
X |
X |
X |
X |
|
Local Functions |
X |
X |
|
|
|
Local Variables |
X |
X |
X |
X |
|
Global variables |
|
X |
X |
|
|
Conditional Output |
X |
X |
X |
X |
|
Support for Multiple Level Conditions |
X |
X |
X |
X |
|
Loops |
X |
X |
X |
X |
|
Html Encoding Shortcuts |
X (ASP.NET 4.0) |
X |
X |
|
|
Global Setting for Html Encoding |
|
X |
X |
X Limited Escape to prevent Html Encoding |
|
View Language Convenience Features |
||||
|
Feature |
WebFormViewEngine |
Spark |
NHaml |
Razor |
|
Global Namespace Imports |
X |
X |
X |
X |
|
Import Namespaces |
X |
X |
|
X |
|
Master Page Features |
||||
|
Feature |
WebFormViewEngine |
Spark |
NHaml |
Razor |
|
Single View Master Page |
X |
X |
|
X |
|
Global Master Page |
|
X |
X |
X |
|
Content Placeholders |
X |
X |
|
X |
|
Special Syntax for Partial Views |
|
X |
X |
|
|
Other Features |
||||
|
Feature |
WebFormViewEngine |
Spark |
NHaml |
Razor |
|
Support for Editor Templates |
X |
X |
|
X |
|
Visual Studio Integration |
X VS 2008/VS 2010 Built in |
X VS 2008/VS 2010Addins |
X VS 2008 Addin |
X VS 2010 - More coming soon |
|
Supports MonoRail |
|
X |
X |
X |
I created the following list of features after porting the views from the NerdDinner (http://nerddinner.codeplex.com/) example to Spark, NHaml, and Razor. The features mentioned are the different features I had to learn in order to convert the views to the different view engines. I’ve grouped the features into four areas: view language, view language convenience, master page features, and miscellaneous features.
Allows you to write multiple lines of code in your views. A related feature is local variables.
An Inline Expression allows you to put a code expression inside static content in the view.
Local Functions provide the ability to create functions that can be called in multiple view locations. A related feature is Multi Line Code Blocks.
Provide a variable that is scoped to the view.
Allows you to provide variables available to all views in your site.
Allows you to use if/else statements to control the output.
Provides the ability to nest if/then statements. Conditional output is a related feature.
Allows you to use loop structures to provide repetitive data output.
Provides shortcuts to ensuring returned output is HtmlEncoded.
Provides the ability to automatically HtmlEncode returned content.
Allows you to import a namespace for a given view so you don’t need to type the fully qualified name of your objects..
Ability to set a namespace import for all views in one location.
Provides the ability to set a master page for a single view.
Ability to have a master page that is used for all views by default.
This feature has two parts. One part allows you to put placeholders in your master page. The second part allows you to specify the content to put in those placeholders. This means a view can optionally populate multiple content placeholders in the master page.
Provides view language syntax to indicate where a partial view should be output. This is an alternative to the MVC’s framework Html helper extension (Html.RenderPartial).
Editor Templates are an MVC 2 feature and allow you to declare what editor template you want loaded on your model classes. Editor Templates are a feature of the ASP.NET MVC framework and are not a feature of the view engine. Not all view engines, however, are able to parse the syntax used to make this work.
Visual Studio integration includes tooling for creating views, as well as IntelliSense and keyword coloring—things we take for granted most of the time.
Ability to use the same view engine with other .NET MVC Frameworks (like Castle’s MonoRail).
|
Website |
|
|
Download site |
|
|
License |
Microsoft Public License (MS-PL) |
|
Last Release |
MVC 2.0 (Last Release), MVC 3 Preview 1 |
|
Creator(s) |
ASP.NET MVC Team at Microsoft |
|
Support Forum |
|
|
Languages Supported |
C#, Visual Basic |
|
ASP.NET MVC Versions |
1.0, 2.0, 3 (Preview 1) |
|
Website |
http://sparkviewengine.com/ |
|
Download site |
http://sparkviewengine.codeplex.com/ |
|
License |
Apache License, Version 2.0 |
|
Last Release |
Spark v1.1 (March 11, 2010) |
|
Creator(s) |
Louis DeJardin |
|
Support Forum |
|
|
Languages Supported |
C#, Visual Basic, Iron Python, Iron Ruby |
|
ASP.NET MVC Versions |
1.0, 2.0 |
|
Website |
http://code.google.com/p/nhaml/ |
|
Download site |
http://code.google.com/p/nhaml/downloads/list |
|
License |
MIT License |
|
Last Release |
Nhaml-2.0-beta-4 (August, 20, 2010) |
|
Creator(s) |
Andrew Peters |
|
Support Forum |
http://groups.google.com/group/nhaml-users |
|
Languages Supported |
C#, Visual Basic, Iron Ruby, Boo, F# |
|
ASP.NET MVC Versions |
1.0, 2.0 |
|
Website |
http://www.asp.net/mvc and http://www.asp.net/WebMatrix |
|
License |
|
|
Last Release |
Pre-releases: WebMatrix Beta and MVC 3 Preview 1 |
|
Creator(s) |
ASP.NET MVC Team at Microsoft |
|
Support Forum |
http://forums.asp.net/1146.aspx |
|
Languages Supported |
C#, Visual Basic |
|
ASP.NET MVC Version |
3 (Preview 1) |
If you’ve written any views in MVC, you’ve used the WebFormViewEngine view engine at one point or another. The syntax for writing views with this engine is the same syntax ASP.NET Web Forms uses for .aspx pages.
The default view engine used by ASP.NET MVC is powerful, mature and can do everything you need to do in a view – after all it stands on the shoulders of ASP.NET. Due to its IViewEngine implementation being in the class named WebFormViewEngine, the ASP.Net MVC default view engine is sometimes called the WebFormViewEngine.
The WebFormViewEngine is different than the other view engines in this review in that it extends ASP.NET’s Page class for its views. This allows the view engine to use a lot of the ASP.NET infrastructure to do the heavy lifting of parsing and compiling the view content.
The file extensions used are also borrowed from ASP.NET: .aspx for views, .ascx for partial views (and editor templates), and .master for master pages.
The following is a screen shot of a view written with the default ASP.NET MVC view engine:
Screenshot 1. NerdDinners Dinner/Index Views using default view engine
The WebFormViewEngine’s history starts with ASP.NET MVC, which hit CTP in December 2007. Currently, it is the only view engine that ships with ASP.NET MVC. This view engine extends ASP.NET’s Page object to provide the view abstraction to MVC. This allows views to be written in the same web forms syntax used in ASP.NET web pages. The server-side escape syntax used by the view engine (<% … %>) dates back to Classic ASP (that is, before .Net).
The base classes used by the WebFormViewEngine—ViewPage, ViewMasterPage, and ViewUserControl—are derived from the ASP.NET relatives Page, MasterPage, and UserControl, respectively, thus providing a mature rendering capability to the view.
The WebFormViewEngine supports fourteen desired features.
|
Feature |
Notes |
|
Multi Line Code Blocks |
|
|
Inline Expression |
|
|
Local Functions |
|
|
Local Variables |
|
|
Conditional Output |
|
|
Support for Multiple Level Conditionals |
|
|
Loops |
|
|
Html Encoding Shortcut |
Only when using ASP.NET 4.0 |
|
Global Namespace Import |
Add in the web.config |
|
Import Namespace |
|
|
Single View Master Page |
2 Options: Page Directive or Change in the Controller |
|
Content Placeholders |
|
|
Support for Editor Templates |
|
|
Visual Studio Integration |
Best support of all view engines |
If you have ASP.NET MVC installed, you already have the WebFormViewEngine installed (if needed, download the ASP.NET MVC framework from http://www.asp.net/downloads).
The WebFormViewEngine is included in the System.Web.Mvc.dll, so the view engine is versioned with the ASP.NET MVC framework you reference. However, some of the features of the WebFormViewEngine are dependent on the version of the .Net framework/ASP.NET you are using. For example, if you are using .Net 3.5, to Html encode an output value you need to use syntax like the following:
If you are using .Net 4.0, you can use the new “Code Nugget” syntax to do the same thing:
Figure 2. ASP.NET 4.0 Html Encoding Syntax
Since syntax is one of the main reasons you may want to use an alternative view engine, I have included some examples of the corresponding key features. This is not meant to be a language reference; rather, it is included to help you get a feel for how you may implement a view using the different view engines:
<% string message = "The time is: {0}"; string timeString = DateTime.Now.ToShortTimeString(); string output = string.Format(message, timeString); %> The result from serverside code is: <%=output %>
|
Code Sample 1. Mult-Line Code Block - ASPX
<script src="<%=Url.Content("~/Scripts/MSAjaxHistoryBundle.js")%>"
type="text/javascript"></script>
|
Code Sample 2. Inline C# Code - ASPX
<script runat="server"> string FunctionCallOnServerSide() { return "Hello World!"; } </script> <div> This is client side html Server Says: <%=FunctionCallOnServerSide() %> and more client side html </div>
|
Code Sample 3. Local Function - ASPX
<% string message = "Hello World"; %> The result message is: <%= message %>
|
Code Sample 4. Local Variable Usage - ASPX
<strong>Who's Coming:</strong> <% if (Model.RSVPs.Count == 0){%> <span>No one has registered.</span> <% } %>
|
Code Sample 5. Conditional Output - ASPX
<% if (Request.IsAuthenticated) { %> <% if (Model.IsHostedBy(Context.User.Identity.Name)) { %> <p>You are the host for this event!</p> <% } else { (Model.IsUserRegistered(Context.User.Identity.Name)) { %> <p>You are registered for this event!</p>
<% } %> <% } else { %>
<strong>RSVP for this event:</strong>
<% } %>
|
Code Sample 6. Multi-Level Condition Output - ASPX
<% foreach (var dinner in Model) { %> <li> <%: Html.ActionLink(dinner.Title, "Details",
new { id=dinner.DinnerID }) %>
</li>
<% } %>
|
Code Sample 7. For Loop - ASPX
|
<%: dinner.Address + " " + dinner.Country %>
|
Code Sample 8. Html Encoding Shortcut
|
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" />
|
Code Sample 9. Global Namespace Import - ASPX
|
<%@ Import Namespace=”NerdDinner.Helpers" %>
|
Code Sample 10. Namespace Import - ASPX
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>
|
Code Sample 11. Setting MasterPageFile attribute in Page Directive - ASPX
public ActionResult About() { var result = View(); result.MasterName = "AlternateMaster"; return result;
}
|
Code Sample 12. Setting MasterName Property of a View - ASPX
<head id="Head1" runat="server"> <title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<asp:ContentPlaceHolder ID="HeadContent" runat="server" />
</head>
|
Code Sample 13. Master Page with ContentPlaceholders - ASPX
<asp:Content ID="TitleArea"
ContentPlaceHolderID="TitleContent"
runat="server">
About
</asp:Content>
<asp:Content
ID="HeadArea"
ContentPlaceHolderID="HeadContent"
runat="server">
<script
src="<%=Url.Content("~/Scripts/jquery-1.4.1.min.js")%>"
type="text/javascript"></script>
</asp:Content>
|
Code Sample 14. View with Content Elements - ASPX
Being the first view engine for ASP.NET MVC also means that the WebFormViewEngine has an advantage over other view engines when it comes to Visual Studio tooling and support. Once you install ASP.NET MVC, you have the default view engine integrated into Visual Studio – which provides a lot of tooling for your views.
The Visual Studio tooling provides an Add View Dialog (shown above) when you create a new view, which provides the following features:
· Ability to specify whether the view is a partial view or normal view
· Choice of the type of view model you want
· Option to choose a T4 template for generating the view content or just create an empty view
· Option to use a master page and set the content placeholder for the view’s content
Visual Studio provides complete IntelliSense support for views using the WebFormViewEngine, including support for the directives used at the top of the page (page settings, namespace imports, etc.) and usage documentation as you type, as well as all other .aspx and .ascx productivity enhancements added by Visual Studio over the years, including ASP.NET MVC, HTML markup, and JavaScript Snippets (new in VS 2010).
Figure 2. Page Directive IntelliSense ![]()
Figure 3. Usage Documentation IntelliSense![]()
Figure 4. ASP.NET MVC Snippet for Action Link Start![]()
Figure 5. ASP.NET MVC Snippet for Action Link Result![]()
The WebFormViewEngine is part of the ASP.NET MVC framework and is supported the same way as the framework. The primary support forum for the ASP.NET MVC framework can be found at: http://forums.asp.net/1146.aspx
· It is included with ASP.NET MVC
· It is supported along with the ASP.NET MVC framework
· It is easy to learn
· Web Form developers can utilize their web form development experience to get started with MVC views
· Hard to test due to a dependency on Page
· The syntax can lead to views becoming “tag soup”
The Spark view engine was designed to mix the code into the flow of the html, which leads to nicer looking views without all the escape syntax that dominates the WebFormViewEngine’s syntax.
Spark has been around for more than two years now and has a lot of features you won’t find in the default view engine. Unlike the WebFormViewEngine, Spark has its own view class and doesn’t extend the ASP.NET Page class. Spark also works with both ASP.NET MVC and Castle MonoRail MVC Framework.
The Spark view engine differs from the other view engines because it mixes its language syntax in with the elements of your view. The language constructs exist mostly as elements and attributes. For example, there is an <if> element and a <for> element, and partial views can also be represented as an element.
The file extension used for views, partial views, and layouts (master pages) is .spark
Below is a screen shot of a view written using the Spark view engine:
Screenshot 2. NerdDinners Dinner/Index View in Spark
Louis DeJardin began developing the Spark view engine in May 2008, soon after reading a blog entry by Phil Haack (Code Based Repeater for ASP.NET MVC). Louis designed and wrote the view engine from ground up (it is not a port of another view engine or language) in order to build a view syntax able to seamlessly mix the code into the Html.
Spark is the only view engine of the four that supports all of the features on my desired feature list.
|
Feature |
Notes |
|
Multi Line Code Blocks |
|
|
Inline Expression |
|
|
Local Functions |
Called ‘Macros’, limited to functions that return strings Also supports global functions. |
|
Local Variables |
|
|
Global Variables |
|
|
Conditional Output |
|
|
Support for Multiple Level Conditionals |
|
|
Loops |
|
|
Html Encoding Shortcut |
|
|
Global Setting for Html Encoding |
|
|
Import Namespaces |
|
|
Global Namespace Imports |
Add in the web.config or _global.spark file |
|
Single View Master Page |
|
|
Global Master Page |
|
|
Content Placeholders |
|
|
Special Syntax for Partial Views |
If partial view file begins with ‘_’ can use the name as an element in the view. |
|
Support for Editor Templates |
|
|
Visual Studio Integration |
Add-ins exist for VS 2008 and VS 2010 |
|
Support for Other MVC Frameworks |
|
Spark is available for download at http://sparkviewengine.codeplex.com/. After you have downloaded and extracted the binaries, add Spark as a view engine in your ASP.NET MVC project by performing the following steps:
1. Set a reference to the Spark.dll and Spark.Web.Mvc.dll libraries included in the release download.
2. Open the Global.asax.cs file and add a new SparkViewFactory to the ViewEngines.Engines collection:
|
void Application_Start() { RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Add(new SparkViewFactory()); }
|
3. In your web.config file, add a section to your configSections for the SparkSectionHandler as shown below:
|
<configuration> <configSections> <section name="spark" type="Spark.Configuration.SparkSectionHandler, Spark"/>
|
4. Add the <spark> section to your web.config file. I put mine just after the </connectionStrings> element. Below is the configuration section I used to port the NerdDinner project to Spark.
|
<spark> <compilation debug="true" defaultLanguage="CSharp" /> <pages automaticEncoding="true" > <namespaces> <add namespace="System"/> <add namespace="System.Collections.Generic"/> <add namespace="System.Linq"/> <add namespace="System.Web"/> <add namespace="System.Web.Mvc"/> <add namespace="System.Web.Mvc.Html"/> </namespaces> </pages> </spark> |
5. Change the settings as necessary (check the documentation for the options that apply to you).
For Spark to work as a view engine in your ASP.NET MVC project, you need references to the Spark.dll and Spark.Web.Mvc.dll
The following are examples for the key features listed above:
|
# string message = "The time is: {0}"; # string timeString = DateTime.Now.ToShortTimeString(); # string output = string.Format(message, timeString);
The result from serverside code is: ${output}
|
Code Sample 15. Using the # Statement to Escape Code -Spark
|
<script src="${ Url.Content("~/Scripts/MSAjaxHistoryBundle.js") }" type="text/javascript"></script>
|
Code Sample 16. Using Inline Escape -Spark
One drawback to using the ${} syntax is that it will fail if the expression returns null. In order to protect against the possibility of an expression returning null, you can use the $!{} expression.
|
<span class="fn nickname">$!{ Model.RSVPs.FirstOrNull() }</span>
|
Code Sample 17. Using Inline Escape When Expression Maybe Null -Spark
|
<macro name="FunctionCallOnServerSide" message="string"> <b>${message}</b> </macro>
<div> This is client side html Server Says: ${ FunctionCallOnServerSide("OutputMessage") } and more client side html </div>
|
Code Sample 18. Declaring and Using a Macro -Spark
|
<var RSVPs = "Model.RSVPs.Reverse()"/> ${RSVPs.Count().ToString() }
|
Code Sample 19. Local Variable Usage -Spark
|
<global version="0.01F" type="float" /> ${version + 1}
|
Code Sample 20. Global Variable Usage -Spark
|
<strong>Who's Coming:</strong> <if condition="Model.RSVPs.Count == 0"> <span>No one has registered.</span> </if> <else> ... other markup here ... </else>
|
Code Sample 21. If/Else Element Usage-Spark
|
<strong>Who's Coming:</strong> <span if="Model.RSVPs.Count == 0">No one has registered.</span> <else> ... other markup here ... </else>
|
Code Sample 22. If Attribute Usage -Spark
|
<if condition="Request.IsAuthenticated"> <if condition="Model.IsHostedBy(Context.User.Identity.Name)"> <p>You are the host for this event!</p> </if> <else if="Model.IsUserRegistered(Context.User.Identity.Name)"> <p>You are registered for this event!</p> </else> <else> <strong>RSVP for this event:</strong> </else>
|
Code Sample 23. Multi-Level Condition Output - Spark
|
<ul class="upcomingdinners"> <for each="var dinner in Model"> <li> !{ Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) } </li> </for> </ul>
|
Code Sample 24. For Element Usage -Spark
|
<ul class="upcomingdinners"> <li each="var dinner in Model"> !{ Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) } </li> </ul>
|
Code Sample 25. Each Attribute Usage -Spark
For html cncoding you can set the automaticEncoding flag in the spark section of your web.config file to turn html encoding on by default. This will cause every ${expr} expression evaluation to be Html encoded.
If you need to specify an expression to not be Html encoded, use the !{ expr } syntax.
|
<spark> <compilation debug="true" defaultLanguage="CSharp" /> <pages automaticEncoding="false" >
|
Code Sample 26. AutomaticEncoding Attribute Set in Web.Config -Spark
Spark provides a utility method that will Html Encode expressions called H( ). However, I recommend having automaticEncoding on via the global setting and using the !{ } syntax for when you don’t want to include Html Encoding.
|
<spark> <compilation debug="true" defaultLanguage="CSharp" /> <pages automaticEncoding="false" > <namespaces> <add namespace="System"/> <add namespace="System.Collections.Generic"/>
|
Code Sample 27. Global Namespace Import -Spark
|
<use namespace="NerdDinner.Helpers" />
|
Code Sample 28. Adding a Namespace Import - Spark
Spark includes a site-wide master page (or layout), which allows you to have all views use a master page without having to specify the name for each of the views.
In order to create a global master page/layout, create an Application.spark file in the Views/Layouts folder or the Views/Shared folder.
You can also create a layout for a specific controller. To do so, create a layout with the same name as the controller.
|
<use master="AlternateMaster" />
|
Code Sample 29. Master Layout Assignment -Spark
|
<head> <use content="HeadContent" /> </head> |
Code Sample 30. Master Layout with Content Elements -Spark
|
<content name="TitleContent"> Upcoming Nerd Dinners </content>
<content name="HeadContent"> # Html.RenderPartial("Masthead", false); </content>
This text will be in the view's main content … notice this is not in a content element.
|
Code Sample 31. View with Multiple Content Elements -Spark
Spark provides a special partial view syntax that,flows better than the HtmlHelper syntax.
If the name of your partial view file begins with an underscore character, you can use an element in your content to represent the partial view (minus the underscore) . For example, in my NerdDinner Spark port, there is a layout named _LoginStatus.spark. This allows me to use the element <LoginStatus /> to represent the output location I want for the contents of that partial view.
Since Spark is a third party view engine, the Visual Studio tooling isn’t as good as the WebFormViewEngine. This means that you currently don’t get the functionality provided by the Add View dialog described in the WebFormViewEngine section and so are stuck working with text or html looking files.
For Visual Studio 2008, there is an msi installer included for the 3-9-2009 Build available on the http://sparkviewengine.com/download site.
For Visual Studio 2010, there is a Visual Studio Addin available in Visual Studio Gallery: SparkSense.
For an open source project, Spark is pretty well documented. The documentation can be found on the project site at http://sparkviewengine.com/documentation.
The Spark View Engine also has a Google Group where you can get answers to your questions. Check it out at http://groups.google.com/group/spark-dev.
The Issue Tracker is available on the on the CodePlex site: http://sparkviewengine.codeplex.com/workitem/list/basic.
· Syntax produces clean views
· Views don’t use the Page class and so are easier to test
· Well documented with a lot of samples included in the download
· Supports other MVC frameworks
· Visual Studio tooling isn’t as good as the default view engine
· Advanced usage of some features can make the views harder for a developer to follow
The NHaml (pronounced enamel) view engine is an implementation of the Rails Haml view engine, and its syntax is an abstraction of usual tag based XHTML syntax used by most view engines. As you can see in the screen shot below, it takes only 19 lines in NHaml to create the same view created with 42 lines in the WebFormViewEngine and Spark view engines. A couple of other differences are that the syntax is white space sensitive and you don’t have to set the type of the view model (it figures it out for you).
The file extension used for views, partial views and layouts (master pages) is .haml
Below is a screen shot of a view written using the NHaml view engine:
Screenshot 3. NerdDinners Dinner/Index View in NHaml
Haml was started in May 2006 by Hampton Catlin and from what I can find on the internet, Andrew Peters introduced NHaml to the world in December 2007. The syntax is pretty much the same as Haml, but the parsing/compiling was all written from the ground up. Like Spark, NHaml does not use the ASP.NET’s Page for its view, it has its own implementation.
NHaml supports 14 features from the desired feature list.
|
Feature |
Notes |
|
Multi Line Code Blocks |
|
|
Inline Expression |
|
|
Local Variables |
|
|
Global Variables |
|
|
Conditional Output |
|
|
Support for Multiple Level Conditionals |
|
|
Loops |
|
|
Html Encoding Shortcut |
|
|
Global Setting for Html Encoding |
|
|
Global Namespace Imports |
Have to add all namespace imports to the web.config |
|
Global Master Page |
|
|
Special Syntax for Partial Views |
|
|
Visual Studio Integration |
Add-in exist for VS 2008 |
|
Support for Other MVC Frameworks |
|
NHaml is available for download from the project’s website at http://code.google.com/p/nhaml/downloads/list. After you have downloaded the release package and extracted its contents, you can add the NHaml view engine to your ASP.NET MVC project by performing the following steps:
1. Set a reference to the NHaml.dll and NHaml.Web.Mvc.dll libraries included in the release download.
2. Open the Global.asax.cs file and add a new NHaml.Web.NHamlMvcViewEngine to the ViewEngines.Engines collection:
|
void Application_Start() { RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Add(new NHaml.Web.Mvc.NHamlMvcViewEngine()); }
|
3. In your web.config file, add a section to your configSections for the NHamlConfigurationSection handler as shown below:
|
<configuration> <configSections> <section name="nhaml" type="NHaml.Configuration.NHamlConfigurationSection, NHaml"/>
|
4. Add the <nhaml> section to your web.config file. As mentioned in the Spark setup, I put mine right after the </connectionStrings> element. Below is the section I used to port the NerdDinner project to NHaml—notice it has more assemblies and namespace declarations than the one provided in the Spark setup:
|
<nhaml autoRecompile="true" templateCompiler="CSharp4" encodeHtml="true" useTabs="true" indentSize="4"> <assemblies> <clear/> <add assembly="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="NerdDinner, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> <add assembly="DDay.iCal, Version=0.80.0.0, Culture=neutral, PublicKeyToken=1790ba318ebc5d56" /> <add assembly="Elmah, Version=1.0.10617.0, Culture=neutral, PublicKeyToken=null" /> <add assembly="DotNetOpenAuth, Version=3.4.4.10162, Culture=neutral, PublicKeyToken=2780ccd10d57b246" /> <add assembly="OpenSearchToolkit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </assemblies> <namespaces> <clear/> <add namespace="System.Collections"/> <add namespace="NerdDinner" /> <add namespace="NerdDinner.Helpers" /> <add namespace="NerdDinner.Models" /> <add namespace="DotNetOpenAuth.OpenId.RelyingParty" /> <add namespace="DotNetOpenAuth.Mvc" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Mvc" /> <add namespace="System.Globalization" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Linq" /> </namespaces> </nhaml>
|
5. Check the configuration documentation at http://code.google.com/p/nhaml/wiki/Configuration to determine your project needs.
In order to be used, the NHaml view engine requires that the following two dll’s be referenced in your MVC project: NHaml.dll and NHaml.Web.Mvc.dll.
Whitespace is important and represents the hierarchy of the final XHTML document being marked up in the view in addition to the scoping of things such as for loops and if statements.
|
- string message = "The time is: {0}"; - string timeString = DateTime.Now.ToShortTimeString(); - string output = string.Format(message, timeString);
The result from serverside code is: =output
|
Code Sample 32. Using CodeBlocks - NHaml
To indicate which code should be evaluated in NHaml, you use either universal interpolation or evaluate output. The Universal Interpolation allows you to leave expressions to be evaluated in the middle of a line even though the evaluate output is typically at the beginning of a line.
|
%a { class="feed" href=#{Url.Action("iCalFeed", "Services", null, "webcal")} }
|
Code Sample 33. Using Inline Expression - NHaml
|
=dinner.Address + " " + dinner.Country
|
Code Sample 34. Using Inline Expression2- NHaml
|
- string message = "The time is a message";
The result from serverside code is: =message
|
Code Sample 35. Local Variable Usage - NHaml
|
^ string PageTitle = "";
- if (PageTitle != "") %title=PageTitle
|
Code Sample 36. Global Variable Usage - NHaml
|
%strong Who's Coming: - if (Model.RSVPs.Count == 0) No one has registered.
|
Code Sample 37. Conditional Output - NHaml
|
- if (HttpContext.Current.Request.IsAuthenticated) - if (Model.IsHostedBy(HttpContext.Current.User.Identity.Name)) %p You are the host for this event! - else if (Model.IsUserRegistered(HttpContext.Current.User.Identity.Name)) %p You are registered for this event! - else %strong RSVP for this event:
|
Code Sample 38. Multi-Level Condition Output - NHaml
|
- foreach (var dinner in Model) %li !=Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID })
|
Code Sample 39. Loop ForEach - NHaml
|
<nhaml autoRecompile="true" templateCompiler="CSharp4" encodeHtml="true" useTabs="true" indentSize="4">
|
Code Sample 40. Global HtmlEncoding Setting - NHaml
|
&= dinner.Address + " " + dinner.Country
|
Code Sample 41. Html Encoding Inline - NHaml
NHaml has the concept of a site-wide master page (or layout) named Application.haml. In the layout, the view’s contents have a special placeholder ‘_’.
|
!!! %html %head _ TitleContent _ HeadArea %body _ |
Code Sample 42. Global Master Page - NHaml
In NHaml, partial views use a ‘_’ as the first character in their file names. This allows you to place the contents of a partial view in another view (or layout) by using the special syntax of _ <viewname>.
|
_ LoginStatus
|
Code Sample 43. Special Syntax for Partial Views - NHaml
Like Spark, NHaml is a third party view engine started by one person, so the Visual Studio tooling isn’t as good as the default view engine, though a few addins have been created by the community. Dave Newman created a syntax highlighting add-in for Visual Studio 2008: http://github.com/snappycode/hamleditor
There is also an Intellisense project for NHaml created by sztupy and available at: http://github.com/sztupy/nhamlsense for Visual Studio 2008.
Although NHaml has been around for a little longer than Spark, it isn’t quite as well documented (less reference material and less samples).
A pretty good language reference can be found on the project’s site at: http://code.google.com/p/nhaml/wiki/NHamlLanguageReference
NHaml also has a Google Group where you can get answers to your questions: http://groups.google.com/group/nhaml-users
As with Spark (don’t forget it is Open Source), you can look at the code and attempt to figure out how things work on your own without using any documentation or support.
· Terse syntax that creates views with less code
· Has a lot of features that help create html
· Views don’t use the Page Class and so are easier to test
· Supports languages other than C# and Visual Basic
· The syntax results in a greater learning curve
· Poorly documented with hard to find samples
· Error messages make it difficult to decipher syntax errors
Razor is a new view engine still under development at Microsoft. The initial pre-release of the view engine became available with the MVC 3 Preview 1 release.
The Razor view engine is being designed to mix the code and markup together—not in the same sense as Spark (there aren’t any elements to represent functionality), but in the sense that the code and markup play well together with minimal control characters. Unlike the WebFormViewEngine, Razor doesn’t extend the System.Web.Page class.
The file extension currently used for views, partial views and layouts (master pages) is .cshtml and also in the future .vbhtml for VisualBasic support.
Below is a screenshot of a view written using the Razor view engine.
Screenshot 4. NerdDinners Dinner/Index View in Razor
No history known about this one.
Razor currently supports 14 features (plus more coming soon) from the desired feature list.
|
Feature |
Notes |
|
Multi Line Code Blocks |
|
|
Inline Expression |
|
|
Local Functions |
Not as of MVC 3 Preview 1, but special HtmlHelper syntax coming soon. |
|
Local Variables |
|
|
Conditional Output |
|
|
Support for Multiple Level Conditionals |
|
|
Loops |
|
|
Global Setting for Html Encoding |
No setting currently , it is just on by default |
|
Import Namespaces |
|
|
Global Namespace Imports |
|
|
Single View Master Page |
|
|
Global Master Page |
|
|
Content Placeholders |
|
|
Support for Editor Templates |
|
|
Visual Studio Integration |
IntelliSense coming soon |
In order to use the Razor view engine, you will need the ASP.NET MVC 3 Preview 1 framework installed. You can download it here: http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=cb42f741-8fb1-4f43-a5fa-812096f8d1e8#filelist
Check the release notes at that same link for any system requirements.
Once you have ASP.NET MVC 3 Preview 1 installed, you will have a new project type for ASP.NET MVC 3 Web Application (Razor).
Currently the Razor view engine is in the System.Web.Mvc.dll and shipped with the MVC 3 Preview 1 framework, so there are no external dependencies if you have already downloaded it as mentioned in the Getting Started section above.
The syntax shown for Razor features is all subject to change, since it is based on a pre-release version.
|
@{ string message = "The time is: {0}"; string timeString = DateTime.Now.ToShortTimeString(); string output = string.Format(message, timeString); }
The result from serverside code is: @output
|
Code Sample 44. Multi Line Code Block - Razor
|
<script src="@Url.Content("~/Scripts/MSAjaxHistoryBundle.js")" type="text/javascript"></script>
|
Code Sample 45. Inline Expression - Razor
|
@{ string message = "Hello World"; }
The result message is: @message
|
Code Sample 46. Local Variable Usage - Razor
|
<strong>Who's Coming:</strong> @if (Model.RSVPs.Count == 0) { <text>No one has registered.</text> }
|
Code Sample 47. Conditional Output - Razor
|
@if (Request.IsAuthenticated) { if (Model.IsHostedBy(Context.User.Identity.Name)) { <p>You are the host for this event!</p> } else if (Model.IsUserRegistered(Context.User.Identity.Name)) { <p>You are registered for this event!</p> } } else { <strong>RSVP for this event:</strong> }
|
Code Sample 48. Mult-Level Conditional Output - Razor
|
@foreach (var dinner in Model) { <li> @Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) </li> }
|
Code Sample 49. Loop - Foreach - Razor
|
<pages> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" />
|
Code Sample 50. Global Namespace Import - Razor
|
@using NerdDinner.Helpers;
|
Code Sample 51. Single View Namespace Import - Razor
Razor has the concept of a site wide master page (or layout) like Spark and NHaml. This allows you to create a master page in the Views/Shared directory named Application.spark and you don’t have to specify the layout page in the individual views.
|
@{ LayoutPage = "~/Views/Shared/_Layout.cshtml"; }
|
Code Sample 52. Single View Master Page - Razor
|
<head> <title>@View.Title</title> @RenderSection("HeadArea", optional:true) </head>
|
Code Sample 53. Master Page with Content Placeholders - Razor
|
@{ View.Title = "Upcoming Nerd Dinners"; LayoutPage = "~/Views/Shared/_Layout.cshtml"; }
@section HeadArea { @Html.Partial("_Head", false) }
This text will be in the view's main content … notice this is not in a section.
|
Code Sample 54. View with Multiple Content Elements/Sections - Razor
Since the Razor view engine is being developed by Microsoft, you will (eventually) get full Visual Studio support. With the ASP.NET MVC 3, Preview 1 release, some tooling has been added – though IntelliSense is not yet working.
There are now two choices for ASP.NET MVC 3 Web Applications – one for the ASPX (WebFormsViewEngine) and the other for Razor.
The add View Dialog box, now allows you to choose which view engine you are using – and Razor is now a choice for you.
Since MVC 3 Preview 1 is a pre-release version, it isn’t a fully supported release – though you can get community (and often team) support in the forums at: http://forums.asp.net/1146.aspx
· Easy to learn
· Views are cleaner than WebFormViewEngine view syntax
· Views don’t use the Page Class – so they are easier to test
· Not yet released – current documentation is minimal with no central location to find information on the view engine.
· Error messages are hard to decipher when you get the syntax wrong
To conclude this Developer Review, I hope we have provided you with enough useful information about the default ASP.NET MVC view engine, Spark, NHaml and Razor to help you decide which is best for you.
After converting the views of the NerdDinner example to Spark, NHaml and Razor here are my thoughts on the different view engines:
Default ASP.NET MVC View Engine
Still my favorite view engine, though the tendency of ending up with views that feel messy does bother me. For client work, I will be using this view engine probably for quite some time – especially if the client is going to be supporting the resulting views.
Spark
Initially the syntax reminded me of the year I used ColdFusion, but once I got past that I really enjoyed the clean look the view has when it is complete. I am really impressed with the functionality it provides as well as the documentation and samples available
for it.
NHaml
At first I wasn’t a fan of the Haml syntax, but its efficiency eventually attracted me to it. I can see using this view engine on my own site in the future, though I don’t think it is a good choice for working on a typical client’s site (if the client will
be supporting it).
Razor
Though this view engine isn’t yet completed, the syntax that Razor uses is really easy to pick up and does leave the view looking better than on the default view engine.
Jason Haley has been working with Microsoft technologies for the past 15 years in various settings – mostly in the New England and Seattle areas. Last year (2009), he decided to become an independent consultant and started Jason Haley
Consulting. Now almost a year of being gainfully unemployed, he is building his client base in the New England area and enjoying the opportunities of working with multiple clients instead of just a single full-time employer. Jason also has a few open source
projects on CodePlex, of which the most popular is PowerCommands for Reflector (http://powercommands.codeplex.com/)
Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums,
or
Contact Us and let us know.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
Wow. Great work! Thanks!
that was an amazing review, thanks!
Thanks! Sooooo helpful!
Nice comparison!
But I think that having many features and producing code that is easy to support is different things. Local functions is for example something that makes it very easy to write messy code.
You should have a look at StringTemplate also. It is different but the views are very clean and it makes testing a lot easier.
websitelogic.net/.../stringtemplate-viewengine-asp-net-mvc
Thank you! I'm looking foward a VS2010 Add-in that support Spark View Engine Intellisense, and I luckily found it on your great post. Thanks again!
Razor does support local functions if you create them as an Action or Func property.
Remove this comment
Remove this thread
close