Inside Razor - Part 2 - Expressions

This is part 2 of my Inside Razor series.  Read Part 1 here.

In my previous post, I glossed over one line in my sample:

<li>@p.Name ($@p.Price)</li>

Well, it's finally time to get in to how this is parsed!  So, to recap from last time, when we see the “<li>” here, we know that we are parsing a block of markup which ends at the “</li>”.  The markup parser scans forward until it finds the end tag, but before it reaches it, it sees an “@”.  So, just as with “@foreach”, it switches to the code parser.

This is where things get a bit different.  The C# code parser looks at that first identifier: “p” and checks its internal list of C# keywords.  Of course, “p” is not a C# keyword, so the C# code parser enters “Implicit Expression” mode.  The algorithm for parsing implicit expressions is something like the following:

  1. First, Read an identifier
  2. Is the next character a “(“ or “[“?
    • Yes - Read to the matching “)” or '”]”, then Go To 2
    • No – Continue to 3
  3. Is the next character a “.”?
    • Yes – Continue to 4
    • No – End of Expression
  4. Is the character AFTER the “.” a valid start character for a C# identifier?
    • Yes – Read the “.” and Go To 1
    • No – DO NOT Read the “.”, and End the Expression

The high-level overview of this algorithm is that an implicit expression is an identifier, followed by any number of method calls (“()”), indexing expressions (“[]”) and member access expressions (“.”).  And, whitespace is not allowed (except for within “()” or “[]”).  So for example, these are all valid implicit expressions in Razor:

@p.Name
@p.Name.ToString()
@p.Name.ToString()[6 - 2]
@p.Name.Replace(“ASPX”, “Razor”)[i++]

However, the following are not valid, and the second section (after the arrow, “==>”) is the only part that would be considered part of the expression by Razor:

@1 + 1 ==> @
@p++ ==> @p
@p    .   Name ==> @p
@p.Name.Length – 1 ==> @p.Name.Length

This is why we have another syntax for expressions: “@(…)”.  This syntax allows anything you want within the “()”.  So, you can write all of the previous examples using that syntax as an escape-hatch:

@(1 + 1) 
@(p++) 
@(p    .   Name) 
@(p.Name.Length - 1)

Once we’ve identified the expression, we pass it along to our code generator.  When generating the code for “@foreach () { … }”, we just dump that code into the generated C# class as-is, but when we identify an expression (either implicit or explicit) we do something a little different.  You probably noticed that unlike ASPX, there is only one control construct: “@”, there is no “@=” to distinguish code that we run vs. expressions that we render the value of.  This is where some of the magic of Razor comes in.  If we see “@foreach” for example, we know that “foreach” is a C# keyword, so that block is written as a statement to be executed.  When we see “@p.Name” or “@(1 + 1)”, we know that they are expressions, so after executing them, we render the result.  So basically:

  • @if, @switch, @try, @foreach, @for, etc. are equivalent to “<% %>”
  • @p.Name, @(p++), @(1 + 1), etc. are equivalent to “<%: %>”

Another side note is that expressions are equivalent to “<%:” and NOT “<%=”.  We made a decision in Razor that HTML encoding should be the default, and that if you want to write unencoded strings, you can use the IHtmlString interface that has been blogged about before.

So, with all that background, we can quickly jump back to our initial sample:

<li>@p.Name ($@p.Price)</li>

When we see “@p.Name” we identify that as an expression, but the space before the “(“ stops us from interpreting it as a method call.  Then “ ($” are all markup and when we see the “@”, we interpret “@p.Price” as an expression and stop at the “)”.

So there’s a quick overview of how Razor identifies and parses expressions.  In my next post I’m going to discuss hosting the Razor parser outside of ASP.Net.  As before, please feel free to leave comments if you have questions, or send me a tweet (@anurse) or an email (andrew AT andrewnurse DOT net).

Monday, July 12, 2010 5:45:54 PM (Pacific Standard Time, UTC-08:00)
Very thanks for this posts Andrew, can you show more interesting things about super hiper ultra time saver view engine Razor?
I'm counting on fingers for asp.net mvc 3
Hugo Freitas
Tuesday, July 13, 2010 11:09:25 AM (Pacific Standard Time, UTC-08:00)
Another great post Andrew! Will you be posting more about the inner workings of Razor's parser and compiler? How to create your customer markup or code parser for example?

Keep 'em coming!
Gus
Wednesday, July 14, 2010 7:30:23 PM (Pacific Standard Time, UTC-08:00)
Issues :

@
{
}
That thing is not allowed, the parser is too strict :P

I hope you and asp.net still get the idea of separating html, css, and script
and keep in mind, not only html which dynamic, but css and script also

Just tested today :
On .cshtml
i can achieve dynamic style, but it's a style inside cshtml doc, which need to be avoided
Any idea how i can do this as separated file ?
Perhaps razor should support .csstyle .csscript ?
(better solution than automatically separate html css script maybe)

<head>
@{
int margin=0;
int padding=0;
string background=@"transparent url('webx-bkg.png') repeat-x";
}
<title>WebMatrix Default Page</title>
<style type="text/css">
html { background: @background; margin: @margin; padding: 0; }

margin and padding work nice,
background not working due html encoding stuff
url(&#39;webx-bkg.png&#39;)
(based on your answer on haacked, no quick solution yet)
Andrew
Thursday, July 15, 2010 9:32:40 PM (Pacific Standard Time, UTC-08:00)
Any eta on the first public beta of razor? Can't wait to try it out.
Mike
Friday, July 16, 2010 8:06:28 AM (Pacific Standard Time, UTC-08:00)
@Mike - Razor is available as part of WebMatrix: http://www.microsoft.com/web/webmatrix. It's only a 15MB download and while it comes with the WebMatrix tool, you can still use Razor in Visual Studio (you just won't have colorization or intellisense, yet). The next beta is expected soon (on the scale of months, not years or days :)).

@Andrew - Yes, "@{" cannot contain any whitespace between it. For "@background" if you change the place where you initialize it to:

string background = new HtmlString("transparent url('webx-bkg.png') repeat-x");

Then when you render it, it will not be HTML Encoded, because you indicated it was already HTML by using a class which implements IHtmlString. We're looking at providing a helper method that would allow you to write something like:

@Literal(background)
or:
@Unescaped(background)

Which would do this wrapping for you.
Friday, July 16, 2010 12:46:36 PM (Pacific Standard Time, UTC-08:00)
Ya I meant the public beta for asp.net mvc :)
Mike
Friday, July 16, 2010 10:40:58 PM (Pacific Standard Time, UTC-08:00)
So,

can you support to generate css and js file with razor syntax ?

since it's possible to have dynamic style; script(havent test) within inline html doc, should not much work, right ?
Andrew
Saturday, July 17, 2010 2:00:55 PM (Pacific Standard Time, UTC-08:00)
@Mike - I believe "later this month" is the official schedule for the MVC preview containing the Razor view engine :)

@Andrew - You could absolutely have a CSHTML file that only contains CSS, set the content-type appropriately (maybe use routing to give it a ".css" extension) and then link it in to your page as a stylesheet. Same for JavaScript.
Monday, July 19, 2010 5:23:16 PM (Pacific Standard Time, UTC-08:00)
Hi,

how to use page directive on razor ?
i can't find any sample on net
Andrew
Tuesday, July 20, 2010 12:16:13 AM (Pacific Standard Time, UTC-08:00)
Razor doesn't have any directives, we felt that everything we needed in a directive could be achieved in code or with more specific syntax. What's the scenario you're trying to accomplish?
Tuesday, August 03, 2010 1:54:41 PM (Pacific Standard Time, UTC-08:00)
Trying to port some of my MVC 2 samples to Razor and hitting a wall.
In each view, I'm setting the Title in the browser this way.

WebForms ViewEngine version:
<%= Model.SiteTitle %> - <%= Model.PageTitle %>

But I'm having problems with Razor.

Razor version:
@Model.SiteTitle - @Model.PageTitle (doesn't work)
@(Model.SiteTitle + " - " + Model.PageTitle) (doesn't work)

Any suggestions?

Thanks
Wednesday, August 04, 2010 8:07:31 AM (Pacific Standard Time, UTC-08:00)
Syntatically, that seems correct. Can you email me (andrew AT andrewnurse DOT net) with more details? I'd like to help you, but I need a little more context. If you could send me the exact error or incorrect rendering you're getting that would be very helpful.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview