Razor - 语法

创建时间:
2014-05-24 23:25
最近更新:
2018-09-17 11:43

Resource - Official Website

  1. ASP.NET 网页 (Razor) API 快速参考

Resource

  1. Introduction to ASP.NET Web Programming Using the Razor Syntax (C#)
  2. 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 245 "了解 Razor 语法"
  3. 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 255 "Razor 与 ASPX 语法比较"
  4. Razor 语法大全
  5. w3cschool.cc 上的文档

Razor 语法 概要

  • 文件扩展名为 .cshtml
  • 默认 "Layout (布局)" 在 _ViewStart.cshtml 文件中设置。
  • _Layout.cshtml 类似于 "MasterPage (母版页)"。
  • @RenderBody()@RenderSection() 类似于 "asp:ContentPlaceHolder 标签"。
  • @{} 类似于 <%%>.
  • @() 类似于 <%:%>.
  • @variable 类似于 <%=variable%>.
  • @ 为标识符。
  • {} 为作用域。
  • 单行使用泛型: @(str.Count<char>())
  • 网站根目录 @Href("~/")

HTML 与 C# 混合编码规则

  • 作用域内,如以 HtmlTag 开始,则视为文本,否则视为代码。
  • 作用域内,如非 HtmlTag 开始、又非代码、又需要直接输出,则需使用 @: 前缀。
  • 在文本中使用 C# 变量需要再以 @ 声明。

总结: 在 文本 与 代码 中切换并输出

在 HTML 中 输出变量的值的字符串表示形式

@code
@(code)

在 代码块 中 输出 字符串

会 HtmlEncode

@code

不 HtmlEncode

@Html.Raw(string)
@:string
<text>string</text>

隐式代码表达式

表达式将被计算,其值将被写入响应中,这就是在视图中显示值的一般原理。
注意: 行末无需分号。

<p>
    当前时间: @DateTime.Now
</p>

显式代码表达式

输出运算结果。

<div>
    Username: @(User.Identity.Name + Model.MemberLevel)
    Status:   @(ViewBag.IsEnabled ? "启用" : "停用")
</div>
<span>ISBN@(isbn)</span>

输出变量的值的字符串表示形式

@ViewBag.Name

以上代码与以下代码等价:

<%: ViewBag.Name %>

以下代码中 Name先生 被解析为属性名,导致语法错误:

您好,@ViewBag.Name先生

改用以下编码可避免以上语法错误:

您好,@(ViewBag.Name)先生
您好,<span>@ViewBag.Name</span>先生
您好,@ViewBag.Name<span>先生</span>
您好,@ViewBag.Name<text>先生</text>

代码块

代码块中,必须符合 C# 语言规范: 每段语句都要由分号结尾。

@{
    var name = "Jack";
    var age = 123;
    @: Hi, my name is @name, I'm @age.
}

等价于:

<%
    var name = "Jack";
    var age = 123;
    Response.Write("Hi, my name is {0}, I'm {1}.", name, age)
%>

代码块中 @s@s; 的区别 - 行末的引号可有可无

@s 后无分号

Razor 源码:

@if(Model.IsLastTb) {
    string s = "...";
    @s
}

.cs 文件中生成以下代码:

Write(s);

@s 后有分号

Razor 源码:

@if(Model.IsFirstTb) {
    string s = @"...";
    @s;
}

.cs 文件中生成以下代码:

Write(s);

      ;

总结

@s@s; 仅生成的 C# 代码不同,无其他区别。

在代码块中混合代码和纯文本

Razor 查找 HTML 标签的开始位置,以确定何时将代码转换为标记。如果想在代码行之后立即输出纯文本,则需要使用以下语法:

  1. 代码块中,由 @: 开头的行将被解释为文本而非代码,HtmlEncode 后直接输出。
  2. <text></text> 和其它 HTML tag 一样,让 Razor 知道这是文本而非代码; 但它不会输出至响应中。使用该语法可输出多行字符串。
@if(ViewBag.IsEnabled != null && ViewBag.IsEnabled){
    <text>
        显示启用的 HTML 段落:
        <p>
            @ViewBag.EnabledMessage
        </p>
    </text>
} else {
    <text>
        显示停用的 HTML 段落:
        <p>
            @ViewBag.DisabledMessage
        </p>
    </text>
}

or

@if(ViewBag.IsEnabled != null && ViewBag.IsEnabled){
    <text>启用</text>
}
else{
    <text>停用</text>
}

笔者个人喜欢采用 <text></text> 方式,因为它具有逻辑意义。
-- 《ASP.NET MVC 3 高级编程》 Page 53

@using

Note: 行末有无分号均可。

Example

未使用 @using:

@model IEnumerable<MySolution.Models.Product>

使用了 @using:

@using MySolution.Models
@model IEnumerable<Product>

公共配置

ASP.NET MVC 项目的 ~/Views/Web.config 文件中的 <system.web.webPages.razor> 配置节下的 <namespaces> 配置节,用于为所有视图统一引用命名空间,避免在所有视图中编写 @using。
参考: 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 267。

@model

Syntax: @model IEnumerable<MySolution.Models.Product>
作用:将当前页面中的 Model 属性的类型由 System.Object 修改为指定类型,从而改善 Intellisense。

@Model

在视图中可以通过 @Model 获取从 Action 传来的 ViewData.Model 数据模型,因为 @ModelViewData.Model 引用着同一个对象实例。如果该页面未使用 @model 声明数据类型,那么,对于 Intellisense 来说,该对象实例的数据类型为 System.Object

@Html.Action() & @{Html.RenderAction();}

System.Web.Mvc.Html.ChildActionExtensions 类下的
public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues) (共6重载)

public static void RenderAction(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues) (共6重载)

摘要对比:
前者: 调用指定子操作方法并以 HTML 字符串形式返回结果。
后者: 使用指定参数和控制器名称调用指定子操作方法,并在父视图中以内联方式呈现结果。

返回对比:
前者: HTML 字符串形式的子操作结果。
后者: void。故不能 @Html.RenderAction(),需要写为 @{Html.RenderAction();}

每个 _Layout.cshtml 中可使用 0 或 无数次。

@Html.Partial() & @{Html.RenderPartial();}

详见本站专文。

@RenderBody()

public HelperResult System.Web.WebPages.WebPageBase.RenderBody() - 本方法 无参数、元重载。在布局页中,将呈现不在指定部分中的内容页部分。返回值: 类型 System.Web.WebPages.HelperResult。要呈现的 HTML 内容。该方法无参数、无重载。

该方法用在 Layout.cshtml 中 且 只能出现一次:

@RenderBody()

@RenderPage()

Note:

  1. 测试记录: path 实参 可以是相对路径,例如 _PartialHeader.cshtml; 或者是以 ~/ 开头的网站根目录相对路径,例如 ~/Views/Shared/_PartialHeader.cshtml
  2. data 参数示例: 调用方: @RenderPage(path, new{ A=99, B="abc"}); 视图中: @PageData["A"]
  3. RenderPartial() 一样,直接输出、不缓存。
  4. @{Html.RenderPartial();} 一样,将指定的视图直接渲染至响应流 (由 pathFileName 标识、而非 ViewName)。通过把 model 包含在第 2 个参数中,例如 @RenderPage("MyView.cshtml", MyModel),可以提供任何 model 给视图。
  5. 每个 _Layout.cshtml 中可使用 0 或无数次。

用法示例:

@RenderPage("~/Views/Shared/_Header.cshtml")
@RenderPage("~/Views/Shared/_Header.cshtml", new{ parm0 = 123, parm1 = "abc" })
@PageData["param"] //获取 RenderPage() 传递过来的参数。

@section & @RenderSection

Tony 常用代码

Definition in _Layout.cshtml:

@section PageCss{
<style type="text/css">
    ...
</style>
}

@section PageJs{
<script type="text/javascript">
    ...
</script>
}

Call in Child.cshtml:

@RenderSection("PageCss", false)
@RenderSection("PageJs", false)
@section PageCss{@Styles.Render("~/Static/Css/Page/KyVl/Home/Edit")}
@section PageJs{@Scripts.Render("~/Areas/Account/Views/RoleOfStaff/IptJs")}
@section PageCss{
@Styles.Render("~/Views/Home/IndexCss")
}

@section PageJs{
@Scripts.Render("~/Views/Home/IndexJs")
}
@section PageJs{@Scripts.Render(
    "~/Bundle/TTree",
    "~/Views/Category/CrudJs"
)}

三目运算

@(ViewBag.IsEnabled ? "启用" : "停用")

if

@if(ViewBag.IsEnabled != null && ViewBag.IsEnabled){
    @:启用
}else{
    @:停用
}

等价于:

<% if(ViewBag.IsEnabled != null && ViewBag.IsEnabled){ %>
    启用
<% }else{ %>
    停用
<% } %>

foreach

<ul>
    @foreach(var item in Request.ServerVariables){
        <li>@item</li>
    }
</ul>

or

@{
    <h2>Team Members</h2>
    string[] names = {
        "James",
        "John",
        "Tom",
    };
    <ul>
        @foreach(var name in names){
            <li>@name</li>
        }
    </ul>
}

return 可用

先说 原理

Razor 中除了 命名空间、@helper@functions 三者之外的内容 全部转译在 Execute() 中。return 将直接退出该方法,其后代码不被执行。

测试记录

Razor 中 return 后的代码不会被执行。以下示例中,</div> 不会输出至客户端:

<div>
@if(Model == null) {
    return;
}
</div>

注释

RazorViewEngine 下可使用以下 4 种注释:

  1. Razor 块注释: @*contents*@,等价于 <%--contents--%>
  2. C# 块注释
  3. C# 行注释
  4. HTML 块注释

以上前三种均为 服务器端注释,仅最后一种输出至 HTML。

转义 @

My Twitter Handle is &#64;hacked

My Twitter Handle is @@hacked
都将输出:
My Twitter Handle is @hacked

字符串中的 双引号 与 @

To embed double quotation marks, use a verbatim string literal and repeat the quotation marks: 要嵌入双引号,请使用逐字字符串文字并重复引号:

<!-- Embedding double quotation marks in a string -->
@{ var myQuote = @"The person said: ""Hello, today is Monday."""; }
<p>@myQuote</p>

Notice that the @ character is used both to mark verbatim string literals in C# and to mark code in ASP.NET pages. 注意 @字符 被用于两个方面: 标记C#中的逐字字符串 和 标记ASP.NET页面中的代码。

泛型

泛型代码包含尖括号,而尖括号会导致 Razor 转回标记。使用 "显式代码表达式" 可解决此问题: @(Html.SomeMethod<T>())

@helper 辅助方法

@* definition: *@
@helper ShowUnitPrice(int price){
    if(price == 0){
        @:免费
    }
    else{
        @price
    }
}

@* caller: *@
foreach(var item in Model){
    <tr>
        <td>
            @item.ProductName
        </td>
        <td>
            @ShowUnitPrice(item.UnitPrice)
        </td>
    </tr>
}

如果要在多个不同的视图中共用某些 @helper 辅助方法,ASP.NET MVC 框架的默认配置为:

  • 必须将这些 @helper 辅助方法放在公共文件中。
  • 该公共文件必须放在 Web 项目根目录下的 App_Code 文件夹中,该文件夹是待殊的 "ASP.NET 文件夹"。
  • 该公共文件的后缀名必须为 cshtml
  • 该公共文件的文件名将用作类名。
  • @helper 辅助方法,放在页面中、或是放在公共文件中,其定义语法完全相同。
  • @helper 辅助方法,放在公共文件中时,调用方须通过类名引用。例如:假设上述 @helper 定义被移到了 ~/App_Code/UiHelper.cshtml 中,那么调用须修改为: @UiHelper.ShowUnitPrice(item.UnitPrice)

参考: 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 262。
Remark: 开发中小型项目时 Tony 通常把全部 @helper 集中在 ~/App_Code/UiHelper.cshtml 文件中。

Resource

  1. ASP.NET MVC3 和 Razor 中的 @helper 语法

@functions 自定义函数

@* definition: *@
@functions{
    public IHtmlString GetYesterday(){
        var theDay = DateTime.Now.AddDays(-1);
        return new HtmlString(theDay.ToShortDateString());
    }
}

@* caller: *@
@GetYesterday()

如果要在多个不同的视图中共用某些 @functions 自定义函数,ASP.NET MVC 框架的默认配置与上述 @helper 完全相同。
唯一的差异是:@functions 自定义函数,放在公共文件中时,必须声明为 static 才能让各页面调用,否则运行时报错 "编译器错误消息: CS0120: 非静态字段、方法或属性“ASP.FileName.MethodName()”要求对象引用"。

参考: 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 266。
Remark: 开发中小型项目时 Tony 通常把全部 @functions 集中在 ~/App_Code/UiFunctions.cshtml 文件中。

todo: 在 @functions{} 中写类。

@helper 辅助方法的确可以很方便地完成辅助方法开发,不过却失去了一些弹性。例如,无法在 @helper 辅助方法中自定义属性 (Property),只能单纯地传入参数,然后格式化成你想要呈现的样子后直接输出。因此,Razor 还提供了 @functions 自定义函数功能,能够让你用接近 C# 类别的方式进一步定义更复杂的辅助方法。

-- 《ASP.NET MVC 4 开发指南 - 黄保翕编著》 Page 266

Example - 随访项目代码

@functions{

    public IHtmlString StringOfType(short? visit_type)
    {
        switch(visit_type) {
            case 1:
                return new HtmlString("危急值随访");
            case 2:
                return new HtmlString("出院病人随访");
            case 3:
                return new HtmlString("个人体检随访");
            case 4:
                return new HtmlString("团体体检随访");
            default:
                return new HtmlString(string.Empty);
        }
    }

    public IHtmlString StringOfStatus(short? status)
    {
        switch(status) {
            case -1:
                return new HtmlString("全部");
            case 0:
                return new HtmlString("未处理");
            case 2:
                return new HtmlString("已处理");
            default:
                return new HtmlString(string.Empty);
        }
    }

    public IHtmlString StringOfResult(short? result)
    {
        switch(result) {
            case 0:
                return new HtmlString("未处理");
            case 1:
                return new HtmlString("未接通");
            case 2:
                return new HtmlString("无效号码");
            case 3:
                return new HtmlString("已接通");
            case 4:
                return new HtmlString("拒绝访问");
            default:
                return new HtmlString(string.Empty);
        }
    }

}

HTML 编码

Razor 语法默认进行 HTML 编码。
将用户输入,经 HTML 编码之后,再输出至页面,能有效防止 XSS (跨站脚本注入攻击)。

不编码、原样输出未经 HtmlEncode 的字符串:

@Html.Raw("<p>本文字将不经编码、直接输出。</p>")
@(new HtmlString(HtmlStr))
@MvcHtmlString.Create(HtmlStr)

JavaScript 中的 HTML 编码

Razor 默认的 HTML 编码行为,对于在 JavaScript 中显示用户输入还不够,例如:

<script type="text/javascript">
    $(function(){
        var message = 'Hello @ViewBag.UserName';
        $('#Message').html(message).show('slow');
    });
</script>

上述代码中,尽管 @ViewBag.UserName 进行了 HTML 编码,但是仍然具有潜在的 XSS 脆弱性,例如,如果用户提供以下的字符串作为用户名,那么 HTML 将被设置为一个脚本标签:
\x3cscript\x3e%20alert(\x27pwnd\x27)@20\x3c/script\x3e

解决方案:
当在 JavaScript 中将用户提供的值赋给变量时,要使用 JavaScript 字符串编码,而不仅仅是 HTML 编码。上述代码应改为:

<script type="text/javascript">
    $(function(){
        var message = 'Hello @Ajax.JavaScriptStringEncode(ViewBag.UserName)';
        $('#Message").html(message).show('slow');
    });
</script>

@Href() 表示网站的根目录

Example

@Href("~/") 表示网站的根目录

Documentation

#region 程序集 System.Web.WebPages.dll, v3.0.0.0
// D:\Test\CodeGen\trunk\packages\Microsoft.AspNet.WebPages.3.0.0\lib\net45\System.Web.WebPages.dll
#endregion

namespace System.Web.WebPages
{
    // 摘要:
    //     提供用于执行和呈现包含 Razor 语法的 ASP.NET 页的对象和方法。
    public abstract class WebPageExecutingBase
    {
        ...
        // 摘要:
        //     使用指定的参数,从应用程序相对 URL 构建绝对 URL。
        //
        // 参数:
        //   path:
        //     要在 URL 中使用的初始路径。
        //
        //   pathParts:
        //     附加路径信息,例如文件夹和子文件夹。
        //
        // 返回结果:
        //     绝对 URL。
        public virtual string Href(string path, params object[] pathParts);
        ...
    }
}

Razor 语法中的便利设计

以下代码末的 . 不会导致错误:

Hello @ViewBag.Name.

以下代码中对 @ 的转义是多余的:

<a href="mailto:james@@gmail.com">send email</a>

类型转换 (仅备份、未整理)

As 系列扩展方法

AsInt()
AsBool()
AsFloat()
AsDecimal()
AsDateTime()

Is 系列扩展方法

IsInt()
IsBool()
IsFloat()
IsDecimal()
IsDateTime()

Example

@("true".AsBool() ? "T" : "F")  //As系列扩展方法。
@("2010-01-01".AsDateTime())    //As系列扩展方法。
@("123".IsInt())                //Is系列扩展方法。

Razor 语法 测试代码

将以下代码保存为 RazorSyntaxTest.cshtml 可直接执行:

@*
每 1 个 Razor 代码行、Razor 代码块、Razor 块注释都向 HTML 输出了 1 个空行、且仅输出 1 个空行。
*@
@{var s = "A string.";}
<!DOCTYPE html>
@*
下面的示例代码块,有4个重点:
1、@{}标识代码块;
2、@variable实现HTML输出;
3、每行代码必须以分号结束;
4、代码块中的缩进不会输出至HTML。
*@
@{
    int i = 9;
    @i;
}
<html>
@*下面3行代码中的括号都是必须的(注意行末的分号会被输出至HTML):*@
@(i++);
@(++i);
@(i + i);
<head>
    @{i = 99;<script>@i;</script>/*Razor智能地区分并处理了script-tag与C#代码。本行@前的缩进不会输出至HTML。*/}
    <title>@s</title>
    @("<p>@i (p-tag已HtmlEncode、@i未执行)</p>")
    @Html.Raw("<p>@i (p-tag未HtmlEncode、@i未执行)</p>")
    @(new HtmlString("<p>@i (p-tag未HtmlEncode、@i未执行)</p>"))
</head>
<body>
    <span>@s;(注意;被输出至HTML)</span>
    <p>@DateTime.Now.ToString("yyyy-MM-hh")</p>

    <ol>
        <li>Test@{@i}Space. 测试@{@i}前后无空格。</li>
        <li>Test@iSpace. 测试@i前后无空格。</li>
        <li>Test @@iSpace. 测试 @@i前有空格。(此行中3处@@都必须转义,否则无法编译)</li>
        <li>Test@i Space. 测试@i 后有空格。</li>
        <li>Test @i Space. 测试 @i 前后都有空格。</li>
        <li>Test@i.ToString()Space. 测试@i.ToString()前后无空格(此行中方法调用前后无空格)。</li>
        <li>Test @i.ToString()Space. 测试 @i.ToString()前有空格(此行中方法调用前必须有空格、而后无需空格)。</li>
    </ol>
    @{
        @:Text;
        <text>注意:
        这两种输出都保留了缩进。</text>
    }
    <div>
        测试注释:
        @{
            //测试注释
            /*测试注释*/
@*测试注释*@
            <!--测试注释-->
        }
    </div>
    <div>
        测试循环:
        @{
            for (var j = 0; j < 3; j++)
            {
            @j
            }
            for (var k = 0; k < 3; k++)
            {
            @:第 @k 个;
            }
            foreach (string item in new string[] { "a", "b", "c" })
            {
            @:No.@item;
            }
            var l = 0;
            while (l < 3)
            {
            @:第@{@(l++)}个。
            }
        }
    </div>
</body>
</html>

测试记录 - Razor Layout 嵌套

以下代码 Tony 在 MVC4 中测试成功:

外层 _Layout.cshtml 代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewData["Title"]</title>
    @Styles.Render("~/Static/Css/Shared/Css")
    @RenderSection("PageCss", false)
</head>
<body>
    <div id="Header">
        @RenderPage("_Header.cshtml")
    </div>
    <div id="Body">
        @RenderBody()
    </div>
    <div id="Footer">
        @RenderPage("_Footer.cshtml")
    </div>
</body>
@Scripts.Render("~/Static/Js/Shared/Js")
@RenderSection("PageJs", false)
</html>

内层 _Layout.cshtml 代码

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section PageCss{@RenderSection("PageCss", false)}
@section PageJs{@RenderSection("PageJs", false)}

<h1>这是嵌套在外层 _Layout.cshtml 中的 _Layout.cshtml</h1>
<p>本段落在内层 _Layout.cshtml 中、在 @RenderBody() 前</p>
@RenderBody()
<p>本段落在内层 _Layout.cshtml 中、在 @RenderBody() 后</p>

内容页代码

@{
    Layout = "~/Areas/Account/Views/User/_Layout.cshtml";
}

@{
    ViewBag.Title = "内容页 Title";
}

@section PageCss{@Styles.Render("~/Static/Css/Page/Account/User/LoginByEmail")}
@section PageJs{@Scripts.Render("~/Static/Js/Page/Account/User/LoginByEmail")}

<h2>这是内容页中的 h2 标签</h2>