Skip to content

Razor

Razor Pages

使用页面 Razor Pages 直接承载访问。mvc 使用控制器承载访问。

按惯例,页面(.cshtml)与model 类(.cshtml.cs)同名, 类名为 页面文件名 + Model, 并且命名空间相同

<!-- 默认所有页面应用位于 Pages 文件夹下,访问 /Contact 为 /Pages/Contact.cshtml
  一般不要放在 Views 目录下,该目录通常给 MVC 项目使用的
-->

@page  <!-- 必须为第一行,表示直接处理请示,不需要控制器 -->
@page "{id:int}" <!-- 对页面接受链接的类型进行了约束,不符合要求的链接返回 404, 如果参数可选,则使用 "{id:int}"  -->
@using RazorPagesIntro.Pages 
@model RazorPagesContacts.Pages.Customers.CreateModel <!--表示页面中绑定的 model 对象, -->
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h2>Separate page model</h2>
<p>
    <!-- Model 为绑定的 model 对象实例 -->
    @Model.Message
</p>

<p>Enter a customer name:</p>

<form method="post">
    <!-- 显示验证出错时的信息 -->
     <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>

    Name:
    <!-- Customer 相当于 @Model.Customer 的简写 -->
    <input asp-for="Customer!.Name" />
    <input type="submit" />
    <!-- 这里会调用  OnPostDeleteAsync(int id) -->
    <!-- asp-route-{value} 绑定变量 -->
    <!-- asp-page-handler 绑定处理程序 OnPost[handler]Async -->
    <button type="submit" asp-page-handler="delete" asp-route-id="@Model.Id">delete</button>

    <!-- asp-page 指定处理的页面应用  -->
    <a class="nav-link text-dark" asp-area="" asp-page="/Info">Info</a>
</form>
//  一个 model 中的处理

// 绑定的数据模型,默认不支持在 get 上绑定(安全原因),需要 [BindProperty(SupportsGet = true)]
[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnGet(){
    return Page();
}

// 进行 post 的处理
public async Task<IActionResult> OnPostAsync()
{
    // BindProperty 数据一般通过 DataAnnotations 进行验证
    if (!ModelState.IsValid)
    {
        // 返回 PageResult 结果,就是显示当前对应的页面
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    // 返回 RedirectToPageResult 进行重定向
    return RedirectToPage("./Index");
}
// 数据规范例子
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
        [Required]
        [StringLength(30)]
        public string Genre { get; set; }

        [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
        [StringLength(5)]
        [Required]
        public string Rating { get; set; }
    }
}

CSS 隔离

比如 Index.cshtml.css (与 Index.cshtml 放在同一级) 给提供给 Index.cshtml 模式

应用会重载这些 css 样式(每个),然后在 _Layout.cshtml 中使用如下语法 <link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" /> 加载隔离的 css

每个需要的标志会被设置一个唯一标识,css 代码会被重载加入标识

<!-- 标识如下为 b-2rog345cu9 -->
<button b-2rog345cu9="" type="button" class="btn btn-primary">Primary</button>
<!-- css 重载为  -->
button[b-m9xndg8ebn] {
    border-color: blueviolet;
}

<!-- 可以通过配置文件中的 CssScope 将一个或是一批文件设置为相同的标识  -->

Razor 类库

使用格式引用 {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, 比如

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

页面布局

Pages/Shared/_Layout.cshtml 设置页面布局,调用 @RenderBody()

Pages/_ViewStart.cshtml 中设置 Layout属性:@{ Layout = "_Layout"; }

在页面中使用 Layout 指定模板


    <!-- 显示当前视图的内容 -->        
    @RenderBody()

    <!-- 局部章节 required 指定必须还是可选   -->
    @RenderSection("Scripts", required: false)

<!-- 视图中调用局部章节   -->
@section Scripts {
     <script type="text/javascript" src="~/scripts/main.js"></script>
}

命名空间

在 Pages/_ViewImports.cshtml 中指定命令空间

导入共用的一些指令

_ViewImports.cshtml 可能是分层的,每个子目录中应该也有效

页面执行前运行

_ViewStart.cshtml 文件 ,也是分层的。每个子文件夹中可以放置一个。会在根目录下的_ViewStart.cshtml 文件执行后进行执行

默认内容就是指定了布局文件

页面URL

页面 url 相对于 Pages 目录

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")
  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")
  • RedirectToPage("/Index", new { area = "Services" }) 带区域时

ViewData

当模式中设置 [ViewData] 属性值时,该属性值会被设置至 @ViewData 中

比如 [ViewData]string Title { get; } = "About"; 可使用 `@ViewData["Title"] 读取

TempData

默认下 使用 cookie 加密保存 TempData 中的数据。这样可以跨页面传递数据

使用属性 [TempData] 时使用 @TempData.Keep, @TempData.Peek, @TempData["xxxx"] 读取

指定处理函数,asp-page

<!-- asp-page-handler, 处理函数为 OnPostJoinListAsync -->
<!-- 生成的链接 https://localhost:5001/Customers/CreateFATH?handler=JoinList -->
<input type="submit" asp-page-handler="JoinList" value="Join" />

自定义路由

@page "item" <!-- 指定路径页面下的相对路径,比如页面为 /info, 则新路径为 /info.page  -->
@page "{id}" <!-- 指定一个参数  -->
@page "/Some/Other/Path" <!-- 指定一个固定的路径  -->
@page "~/Some/Other/Path" <!-- ~ 开头,相对于根路径 -->
@page "{handler?}" <!-- 会将修改 asp-page-handler="JoinList" 中生成的路径,比如这段生成为 https://localhost:5001/Customers/CreateFATH/JoinList -->

高级配置

builder.Services.AddRazorPages(options =>
{
    options.RootDirectory = "/MyPages";
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

// 指定 page 从应用的根目录开始,默认为在 /Pages 中
builder.Services.AddRazorPages().WithRazorPagesAtContentRoot()

// 指定 page 从指定的目录开始,默认为在 /Pages 中
builder.Services.AddRazorPages().WithRazorPagesRoot("/path/to/razor/pages");

语法

<!-- 转义 @@ -->
<p>@@Username</p>

<!-- 隐式代码, @ 开头 -->
<p>@DateTime.Now</p>

<!-- 显式代码,@() 里面的内容会输出 -->
<!-- @() 中的内容如果是 IHtmlContent, 则调用  IHtmlContent.WriteTo 输出,
    不然调用  toString 输出 -->
<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>

<!-- 使用原样输出 -->
@Html.Raw("<span>Hello World</span>")

<!-- @{} 为代码块,代码块中的内容默认不输出
    代码块中可以定义本地函数进行输出 -->
@{
    <!-- 定义本地函数进行输出 -->
    void RenderName(string name) { <p>Name: <strong>@name</strong></p>    }

    RenderName("Mahatma Gandhi");
    RenderName("Martin Luther King, Jr.");
}

<!-- 代码块中输出内容, 使用 @: 格式  -->
@{
    var inCSharp = true;
    <p>Now in HTML, was in C# @inCSharp</p> <!-- 隐式输出 -->
    <text>Name: @person.Name</text> <!-- text 标签显式输出 -->
    @:Name: @person.Name <!-- @: 输出整行的内容 -->
}


<!-- @for, @foreach, @while, and @do while @using @try, catch, finally @lock -->
直接使用  @xxx {} 即可

<!-- 注释 /* // @**@ -->
@{
    /* C# comment */
    // Another C# comment
}

@*  xxxx *@

<!-- 将指定的属性添至类中 -->
@attribute [Authorize]

<!-- @code 中的代码直接添加至类中, 与 @functions 似乎功能一样 -->
@code {
    // C# members (fields, properties, and methods)
}

<!-- 指定接口与父类 -->
@implements IDisposable
@inherits TypeNameOfClassToInheritFrom

<!-- @inject 注入 -->
@inject IConfiguration Configuration

<!-- @layout 指定布局组件  -->
@layout DoctorWhoLayout

<!-- 指定页面中绑定的 Model 对象 -->
@model TypeNameOfModel

<!-- @namespace 指定命名空间 -->
使用导入文件 ```_ViewImports.cshtml``` 或是 ```_Imports.razor ``` 指定根命名空间,然后 + 目录名 为完整命名路径 

<!-- @page 指定当前为 Razor 页及指定路由 -->

<!-- @preservewhitespace 删除一些空白 -->

<!-- @section 指定布局中的片段  -->

模板化 Razor 委托

使用 @... 指定一段 ui 片段,其中可以使用 @item 表示传入的参数。以方便多次进行调用

<!-- 定义片段 @item 为传入的参数  -->
Func<dynamic, IHtmlContent> petTemplate =  @<p>You have a pet named <strong>@item.Name </strong>.</p>;

<!--调用 -->
@petTemplate(pet)
<!-- 另外一种调用方式 -->
@functions {
    public static IHtmlContent Repeat(IEnumerable<int> idx, int times,
        Func<dynamic, IHtmlContent> template) // template 为 razor ui 片段函数
    {
        var html = new HtmlContentBuilder();

        foreach (var item in idx)
        {
            for (var i = 0; i < times; i++)
            {
                html.AppendHtml(template(item));
            }
        }

        return html;
    }

}

@* @<li>@item</li> 不片段函数 *@
@Repeat(System.Linq.Enumerable.Range(0, 10), 3, @<li>@item</li>)

Razor UI 类库

ASP.NET Core 的类库中的可重用 Razor UI | Microsoft Learn

代码可以直接引用

page 中引用 _content/{PACKAGE ID}/

标记帮助程序

引入@addTagHelper *, AuthWebApp AuthWebApp 为程序集名,非命名空间

  1. 继承 TagHelper, 约定使用名称 xxxTagHelper, 则处理标签 xxx

自定义帮助程序

    // 根据类名决定待处理的标签,这里进行如下处理 <email @domain=xxx UserInfo=info>test</email> -> <a href=mailto />
    [HtmlTargetElement("email")]
    public class EmailTagHelper : TagHelper
    {
        // 绑定标签中的属性,默认为同名属性,或是比如 'domain-name'
        [HtmlAttributeName("domain")]
        public string? DomainName { get; set; }

        // 绑定一个属性
        public UserInfo UserInfo { get; set; }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "a"; // 设置输出的真实的标签类型
            TagHelperContent content = await output.GetChildContentAsync();

            // 设置标签的内容 
            var target = $@"{content.GetContent()}@{this.DomainName}";
            output.Attributes.SetAttribute("href", $@"mailto:{target}");
            output.Content.SetContent(target);

            await base.ProcessAsync(context, output);
        }
    }


    [HtmlTargetElement("bold")] // 指定处理 bold 标签 <bold   />
    [HtmlTargetElement(Attributes = "bold")] // 指定处理带有属性 bold 的标签, <xxx bold />
    public class BoldTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.Attributes.RemoveAll("bold");
            output.PreContent.SetHtmlContent("<strong>");
            output.PostContent.SetHtmlContent("</strong>");
        }
    }

表单中的标记帮助程序

ASP.NET Core 表单中的标记帮助程序 | Microsoft Learn

form 相关参数

<!-- form 相关参数  -->
asp-controller  控制器的名称。
asp-action  操作方法的名称。
asp-area    区域名称。
asp-page    Razor 页面的名称。
asp-page-handler    Razor 页面处理程序的名称。
asp-route   路由的名称。
asp-route-{value}   单个 URL 路由值。 例如,asp-route-id="1234"。
asp-all-route-data  所有路由值。
asp-fragment    URL 片段。

<form asp-controller="Demo" asp-action="Register" method="post">
    <!-- Input and Submit elements -->
</form>


asp-for

使用 显示标签,代替以前的 @Html.xxxxfor,使用时不需要使用 Model 前缀

<!-- asp-for 程序根据类型或属性设置不同的 type,  -->
<input asp-for="<Expression Name>"> <!--  asp-for="Property1.Property2" 相当于 asp-for=(m => m.Property1.Property2) -->
Bool    type="checkbox"
String  type="text"
DateTime    type="datetime-local"
Byte    type="number"
int type="number"
Single、Double   type="number"

[EmailAddress]  type="email"
[Url]   type="url"
[HiddenInput]   type="hidden"
[Phone] type="tel"
[DataType(DataType.Password)]   type="password"
[DataType(DataType.Date)]   type="date"
[DataType(DataType.Time)]   type="time"

<!-- bool 属性(type=checkbox)未选中时 submit 不会提交,所以会自动生成一个 hidden 属性, 效果如下 -->
<!-- 如果 checkbox 未选中,则提交 hidden 值,如果 checkbox 提交,则两个都会提示,但是 checkbox 在前,所以读取的是真实值  -->
<input name="IsChecked" type="checkbox" value="true" />
<input name="IsChecked" type="hidden" value="false" /> 

<!-- @Html.Editor() 和 @Html.EditorFor() 可传入一个字典属性值  -->
@Html.EditorFor(model => model.YourProperty, 
  new { htmlAttributes = new { @class="myCssClass", style="Width:100px" } })

<!-- @Html.xxx 的一些说明  -->
http://vito-note.blogspot.com/2013/01/htmlhelper-class.html


验证消息

<!-- 为 Email 属性(DataAnnotation )生成验证消息 -->
<span asp-validation-for="Email"></span>  
<!-- 自动生成如下内容 ==> <span class="field-validation-valid"  data-valmsg-for="Email"  data-valmsg-replace="true"></span> -->

<!-- All|ModelOnly|None 所有错误|模型错误|无 显示验证消息 -->
<div asp-validation-summary="ModelOnly"></div>

select

<!-- 选中属性绑定 Country, 列表源为 Countries, 类型为 SelectListItem, 要分组的话,设置 Group 属性  -->
<!-- 如果 asp-for 绑定列表,则为多选  -->
<select asp-for="Country" asp-items="Model.Countries"></select> 

<!-- 列表源为枚举值,可以使用属性名 Display -->            
<select asp-for="EnumCountry"  asp-items="Html.GetEnumSelectList<CountryEnum>()">


内置帮助程序

ASP.NET Core 中的定位点标记帮助程序 | Microsoft Learn

一些记录

<!-- 将内容使用服务器缓存应用缓存起来, 其它参数见手册 -->
<cache>@DateTime.Now</cache>

<!-- 使用分布式缓存缓存内容,需要指定 name, 其它参数见手册   -->
<distributed-cache name="my-distributed-cache-unique-key-101">
    Time Inside Cache Tag Helper: @DateTime.Now
</distributed-cache>

<!-- 根据环境值决定要不要显示内容  -->
<environment include="Staging,Production" exclude="Development">
    <strong>IWebHostEnvironment.EnvironmentName is Staging or Production</strong>
</environment>

<!-- https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-form-tag-helper -->
<form >

<!-- 破坏缓存 asp-append-version -->
<img src="~/images/asplogo.png" asp-append-version="true">

<!-- https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper -->
<input />

<!-- cdn 访问不成功,回退访问其它链接 https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/tag-helpers/built-in/link-tag-helper?view=aspnetcore-7.0 -->
<link rel="stylesheet" href="" asp-fallback-href="" />
<script asp-fallback-src="" ></script>

<!-- partial 部分视图 详细见后 -->
<!-- name 指定视图路径, for 指定当前绑定在页面 Model 的 Product 属性, view-data 传入一个 ViewDataDictionary 数据作为视图的 ViewData 部分 -->
<partial name="Shared/_ProductPartial.cshtml" for="Product" view-data="ViewData">

分部视图 partial

见后面的说明

高级

载入共享的组件

使用 ASP.NET Core 中的应用程序部件共享控制器、视图、Razor Pages 等 | Microsoft Learn

通过 IApplicationModelProvider 修改系统配置

使用 ASP.NET Core 中的应用程序模型 | Microsoft Learn

Area

将程序分为多个功能组。每个功能组都有自己的一组 Razor Pages、控制器、视图和模型

ASP.NET Core 中的区域 | Microsoft Learn

创建 Areas 文件夹

将  _ViewImports.cshtml 放在各个不同的 area 目录下

// 修改 Areas 所在目录
builder.Services.Configure<RazorViewEngineOptions>(options =>
{
    options.AreaViewLocationFormats.Clear();
    options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");
    options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
    options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});

控制器

[Area("Products")] public class ManageController : Controller{}

路由设置


app.MapControllerRoute(
    name: "MyArea",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

// 或是
app.MapAreaControllerRoute(
            name: "MyAreaProducts",
            areaName: "Products",
            pattern: "Products/{controller=Home}/{action=Index}/{id?}");

应用区域

<!-- 以下三种方式  -->

<a asp-area="Products" asp-controller="Home" asp-action="About"> Products/Home/About </a>
@Html.ActionLink("Product/Manage/About", "About", "Manage", new { area = "Products" })
<a href='@Url.Action("About", "Manage", new { area = "Products" })'> Products/Manage/About </a>

筛选器

在请求处理管道中的特定阶段之前或之后运行代码

ASP.NET Core 中的筛选器 | Microsoft Learn

每种筛选器类型都在筛选器管道中的不同阶段执行:

授权筛选器:
    首先运行。
    确定用户是否获得请求授权。
    如果请求未获授权,可以让管道短路。

资源筛选器:
    授权后运行。
    OnResourceExecuting 在筛选器管道的其余阶段之前运行代码。 例如,OnResourceExecuting 在模型绑定之前运行代码。
    OnResourceExecuted 在管道的其余阶段完成之后运行代码。

操作筛选器:
    在调用操作方法之前和之后立即运行。
    可以更改传递到操作中的参数。
    可以更改从操作返回的结果。
    不可在 Razor Pages 中使用。

终结点筛选器:
    在调用操作方法之前和之后立即运行。
    可以更改传递到操作中的参数。
    可以更改从操作返回的结果。
    不可在 Razor Pages 中使用。
    可以在操作和基于路由处理程序的终结点上调用。
    异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

结果筛选器:
    在执行操作结果之前和之后立即运行。
    仅当操作方法成功执行时才会运行。
    对于必须围绕视图或格式化程序的执行的逻辑,会很有用。

视图组件

与 分部视图 要复杂一些,功能更加强大

ASP.NET Core 中的视图组件 | Microsoft Learn

派生自 ViewComponent 使用 [ViewComponent] 属性修饰类,或者从具有 [ViewComponent] 属性的类派生 创建名称以后缀结尾的类 ViewComponent 如果非视图组件可以使用 [NonViewComponent] 明确标注为非视图 InvokeAsync 或是 Invoke 返回 IViewComponentResult 放在 Components 子目录中,或是使用程序修改目录名。详细见手册

调用方法

<!-- 调用方法1, 页面中使用    -->
@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})
比如
@await Component.InvokeAsync("PriorityList",
                new {    maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  } )

<!-- 调用方法2, 需要 @addTagHelper *, MyWebApp  -->    
<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">

<!-- 比如 -->
</vc:[view-component-name]>
     <vc:priority-list max-priority=maxPriority is-done=isDone>
</vc:priority-list>

<!-- 控调用方法2, 控制器中返回 -->
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

视图代码

public class PriorityListViewComponent : ViewComponent
{
    // 参数为调用时传入的参数值
    public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);  // 作为视图的 model
    }
}

视图页面

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h3>Priority Items</h3>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

分部视图

ASP.NET Core 中的分部视图 | Microsoft Learn

我的理解,分部视图,只有视图部分,它不带有代码处理。只有处理一些简单布局,比较轻量

一般建议使用 _前缀作为文件名,以区分视图

调用

<!-- 可以使用如下方式调用  -->
<!-- 可以传入 ViewDataDictionary, 或是传入 Model -->


public IActionResult OnGetPartial() =>
    new PartialViewResult
    {
        ViewName = "_AuthorPartialRP",
        ViewData = ViewData,
    };

public IActionResult OnGetPartial() =>
    Partial("_AuthorPartialRP");

<partial name="_PartialName" />
<partial name="_PartialName.cshtml" />
<partial name="~/Pages/Folder/_PartialName.cshtml" />
<partial name="../Account/_PartialName.cshtml" />

@await Html.PartialAsync("_PartialName")
@await Html.PartialAsync("_PartialName.cshtml")
@await Html.PartialAsync("~/Pages/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Pages/Folder/_PartialName.cshtml") 

@{
    // 因为不返回 IHtmlContent, 必须在 @{} 中调用 
    await Html.RenderPartialAsync("_AuthorPartial");
}  

<!-- 不建议调用同步调用,因为会死锁 -->   


页面

@using PartialViewsSample.ViewModels
@model ArticleSection

<h3>@Model.Title Index: @ViewData["index"]</h3>
<div>
    @Model.Content
</div>

显示模板

使用 DisplayTemplates 和 EditorTemplates | Microsoft Learn

显示一个类的内容

放在 DisplayTemplates 目录中

文件名以显示类命名,比如 Address.cshtml 显示 Address 类内容

调用 @Html.DisplayFor(model => model.Address), 也可以指定不同的页面名称

编译器模板

显示一个类的编辑内容

放在 EditorTemplates 目录中

文件名以显示类命名,比如 Address.cshtml 显示 Address 类内容

调用 @Html.EditorFor(model => model.Address)

上传文件

https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-7.0

使用 ASP.NET Core 中的应用程序部件共享控制器、视图、Razor Pages 等 | Microsoft Learn