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 类库
代码可以直接引用
page 中引用 _content/{PACKAGE ID}/
标记帮助程序
引入
@addTagHelper *, AuthWebApp
AuthWebApp
为程序集名,非命名空间
- 继承 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>");
}
}
表单中的标记帮助程序
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>()">
内置帮助程序
一些记录
<!-- 将内容使用服务器缓存应用缓存起来, 其它参数见手册 -->
<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 修改系统配置
Area
将程序分为多个功能组。每个功能组都有自己的一组 Razor Pages、控制器、视图和模型
创建 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>
筛选器
在请求处理管道中的特定阶段之前或之后运行代码
每种筛选器类型都在筛选器管道中的不同阶段执行:
授权筛选器:
首先运行。
确定用户是否获得请求授权。
如果请求未获授权,可以让管道短路。
资源筛选器:
授权后运行。
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