Mvc 基础知识
Mvc 基础知识
参考资料
- 官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/?view=aspnetcore-7.0&tabs=windows
概念
模型-视图-控制器 (MVC) 体系结构模式将应用程序分成 3 个主要组件组:模型、视图和控制器。
此模式有助于实现关注点分离。
模型 (M) 表示应用程序业务逻辑或操作的状态并维护数据。
- 从数据库中检索数据并将其提供给视图或对其进行更新。
- 更新后的数据将写入到数据库。
视图 (V) 负责通过用户界面展示内容,其中的任何逻辑都必须与展示内容相关。
控制器 (C) 是处理用户请求,使用模型并选择要呈现的视图的组件。
- 要阻止控制器逻辑变得过于复杂,请将业务逻辑推出控制器并推入域模型。
- 如果发现控制器操作经常执行相同类型的操作,可将这些常见操作移入筛选器。
- 主要任务:
- 处理浏览器请求。
- 检索模型数据。
- 调用返回响应的视图模板。
约定:
- 控制器由Controller类实现,视图一般是扩展名为 cshtml 的文件,而模型则是只有属性的普通C#类
- 控制器类的名字一般以 <控制器名字>Controller 格式,并且被放到 Controllers 文件夹下
- 视图一般被放到 Views 文件夹下的 <控制器名字> 的文件夹下,视图名字与控制器的方法名字一致
- 实体模型一般与 <控制器名字>ViewModel 格式,放在 ViewModel 文件夹下
- 数据模型直接使用 <控制器名字>,放在 Models 文件夹下
项目组织结构
主要功能
路由
- 建立在 ASP.NET Core 的路由之上,是一个功能强大的 URL 映射组件
- 基于约定的路由:
routes.MapRoute(name: "Default", template: "{controller=Home}/{action=Index}/{id?}");
- 属性路由:
[Route("api/[controller]")]
、[HttpGet("{id}")]
- 基于约定的路由:
依赖关系注入
- 控制器可通过其构造函数注入
- 视图使用
@inject
指令注入:@inject SomeService ServiceName
筛选器 Filter
- 筛选器允许操作方法运行自定义预处理和后处理逻辑,可以作为属性应用于控制器或操作(也可以全局运行)。
模型绑定
- 客户端请求数据直接转换为控制器 Action 中的参数
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ... }
模型验证
使用数据注释验证属性修饰模型对象来支持验证
public class LoginViewModel { [Required] [EmailAddress] public string Email { get; set; } }
控制器检查验证
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { if (ModelState.IsValid) /* code */ }
区域 Areas
- 将大型 ASP.NET Core MVC Web 应用分区为较小功能单元,每个单元都有自己的逻辑组件视图、控制器和模型。
Razor 视图引擎
Razor 用于在服务器上动态生成 Web 内容
强类型视图 :视图具备类型检查和 IntelliSense 支持
@model IEnumerable<Product> <ul> @foreach (Product p in Model) { <li>@p.Name</li> } </ul>
标记帮助程序 :定义自定义标记(例如
<environment>
),或者修改现有标记的行为(例如<label>
)<a asp-controller="Account" asp-action="Login">
视图组件 :可以包装呈现逻辑并在整个应用程序中重用它
分部视图 :分部视图是在
.cshtml
“视图”文件夹 (MVC) 或 Pages 文件夹 (Pages Razor) 中维护没有@page
指令的标记文件。
控制器 Controller
- 控制器类可以不显式地继承自任何类,也可以继承 ControllerBase 类
- mvc 中的控制器是一个 UI 级别的抽象。
- 它的职责是确保请求的数据有效,并选择应当返回的视图(或 API 的结果)。
- 它不会直接包括数据访问或业务逻辑。 相反,控制器会委托给处理这些责任的服务(服务层处理业务逻辑)。
- 控制器上的公共方法(除了那些带有
[NonAction]
属性的方法)均为操作 Action。 - Action 的 ViewModel 会与视图中的模型绑定并进行验证
ModelState.IsValid
- Action 仅仅将请求映射到业务关注点的逻辑,并不直接处理业务,业务关注点有注入的服务来提供
- 返回
IActionResult
实现类时会进行内容协商
Razor 语法
@ 符号
- 使用
@
符号将 C# 代码转换为 Html - C# Razor 关键字必须使用
@(@C# Razor Keyword)
进行双转义 @
符号需要使用@@
进行转义- 电子邮件地址的 HTML 属性和内容的
@
符号不需要转义:<a href="mailto:Support@contoso.com">Support@contoso.com</a>
- 支持可缩放的向量图形 SVG 元素
表达式
- 隐式 Razor 表达式
- 以
@
开头,后跟 C# 代码,不能有空格 @await
可以有空格- 隐式表达式不能包含 C# 泛型,尖括号会被理解为标记
- 以
- 显式 Razor 表达式
@(exp)
- 括号里不会收到空格的影响,整个括号被理解为一个 C# 表达式
- 表达式的计算结果将进行 HTML 编码并直接通过
IHtmlContent.WriteTo
呈现,如果结果不是字符串,将会调用ToString()
方法 HtmlHelper.Raw
输出不进行编码,用户输入可能包含恶意的JavaScript
或其他攻击。
代码块
@{ code }
代码块内的 C# 代码不会呈现,代码块只做 C# 代码的逻辑,结果变量需要借助表达式呈现
一个视图中的代码块和表达式共享相同的作用域并按顺序进行定义
显式行转换为 HTML 使用
@:
@for (var i = 0; i < people.Length; i++) { var person = people[i]; @:Name: @person.Name }
Razor 自动省略不需要的属性,如果传入的值是
null
或false
,则不会呈现特性。<div class="@false">False</div> <div class="@null">Null</div>
控制结构
条件语句
@if, else if, else; @switch
@using
: 用于自动释放对象@using (Html.BeginForm()) { <div> Email: <input type="email" id="Email" value=""> <button>Register</button> </div> }
异常处理
@try, catch, finally
@lock
:保护关键节 section注释:C# 和 html 均支持
指令
@attribute
:将给定的属性添加到生成的页或视图的类中@functions
:将 C# 成员(字段、属性和方法)添加到生成的类中@implements
:为生成的类实现接口@inherits
:对视图继承的类提供完全控制@inject
:将服务从服务容器注入到视图@namespace
:设置生成的 Razor 页面、MVC 视图或 Razor 组件的类的命名空间@using
:向生成的视图添加 C#using
指令@model
:指定传递到视图或页面的模型类型(仅适用于 MVC 视图和 Razor Pages (.cshtml
) )@section
:节布局(仅适用于 MVC 视图和 Razor Pages (.cshtml
))@layout
:为具有 @page 指令的可路由 Razor 组件指定布局(仅适用于 Razor 组件 (.razor
))@code
:将 C# 成员 (字段、属性和方法) 添加到组件(仅适用于 Razor 组件 (.razor
))@preservewhitespace
:删除前后空格(仅适用于 Razor 组件 (.razor
))@page
:- 在
.cshtml
文件中表示该文件是 Razor 页面 - 在
.razor
中指定 Razor 组件应直接处理请求
- 在
指令属性
- 指令属性通常会更改元素的分析方式或启用不同的功能,使用隐式表达式表示
- 仅适用于 Razor 组件 (
.razor
)的指令属性:@attributes
允许组件呈现未声明的属性@bind
属性实现组件中的数据绑定@bind:culture
属性与@bind
属性一起使用以提供 System.Globalization.CultureInfo 用于分析和设置值的格式@on{EVENT}
属性为组件提供事件处理功能@on{EVENT}:preventDefault
属性禁止事件的默认操作@on{EVENT}:stopPropagation
停止事件的事件传播@key
指令属性使组件比较算法保证基于键的值保留元素或组件@ref
提供了一种引用组件实例的方法,以便可以向该实例发出命令@typeparam
指令声明生成的组件类的泛型类型参数
模板化 Razor 委托
@{
// 定义模板
Func<dynamic, object> petTemplate = @<p>You have a pet named <strong>@item.Name</strong>.</p>;
}
@foreach (var pet in pets)
{
// 使用模板
@petTemplate(pet)
}
检查为视图生成的 Razor C# 类
- 默认情况下,不会发出生成的代码文件。
- 若要启用发出代码文件,请将项目文件中的指令 (
.csproj
) 设置为 :EmitCompilerGeneratedFiles:true
- SDK 会在Razor项目根目录中生成一个
obj/Debug/net6.0/generated/
目录
标记帮助程序
概述
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。
标记帮助程序使用 C# 创建,基于元素名称、属性名称或父标记以 HTML 元素为目标。
使用
@addTagHelper
添加标记帮助程序 :@addTagHelper *, AuthoringTagHelpers
使用
@removeTagHelper
删除标记帮助程序使用
_ViewImports.cshtml
文件控制标记帮助程序范围使用标记帮助程序选择退出字符(“!”),可在元素级别禁用标记帮助程序
<!span asp-validation-for="Email" class="text-danger"></!span>
使用
@tagHelperPrefix
指定一个标记前缀字符串, 只有使用前缀th:
的元素才支持标记帮助程序@tagHelperPrefix th: <th:input asp-for="Password" class="form-control"/>
创建标记帮助程序
演示功能目标:将标记
<email>Support</email>
转换为<a href="mailto:Support@contoso.com">Support@contoso.com</a>
约定:
- 标记帮助类放在
TagHelpers
文件夹下 - 类名称的后缀是
TagHelper
,使用标记时自动移除TagHelper
并把首字母小写 - 标记帮助程序采用 Pascal 大小写格式的类和属性名将转换为各自相应的短横线格式,
MailTo
转换为<email mail-to="..." />
- 标记帮助类放在
标记帮助程序需要实现
ITagHelper
接口,通常从TagHelper
派生,这样可以访问Process
方法public class EmailTagHelper : TagHelper { private const string EmailDomain = "contoso.com"; // Can be passed via <email mail-to="..." />. public string MailTo { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "a"; // Replaces <email> with <a> tag var address = MailTo + "@" + EmailDomain; output.Attributes.SetAttribute("href", "mailto:" + address); output.Content.SetContent(address); } }
添加到
Views/_ViewImports.cshtml
文件@addTagHelper [程序集名称].TagHelpers.EmailTagHelper
TagHelperOutput
的属性和方法可操作返回原始的相关属性和内容使用
[HtmlAttributeName]
特性将不同的属性名称映射到属性[HtmlAttributeName("recipient")] public string? MailTo { get; set; } /* <email recipient="…"/> */
使用
[HtmlTargetElement]
指定目标元素
内置 ASP.NET Core 标记帮助程序
定位点标记帮助程序:
asp-
属性的值决定呈现的定位点元素的href
属性值<a asp-protocol="https" asp-host="microsoft.com" asp-controller="Home" asp-action="About">About</a> <!-- href="https://microsoft.com/localhost/Home/About" asp-protocol 用于指定协议 asp-host 用于指定主机 asp-controlle 用于指定控制器 asp-action 用于指定Action --> <a asp-controller="Speaker" asp-action="Detail" asp-route-speakerid="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a> <!-- 根据 Action 的路由拼接 QueryString 参数 如果 asp-route-{value}中的 value 与路由参数或[ModelName]前缀匹配:href="/Speaker/Detail?speakerid=12" 否则:href="/Speaker/Detail?speakerid=12" --> <!-- 控制器路由采用命名的路由 [Route("/Speaker/Evaluations", Name = "speakerevals")] --> <!-- 自定义字典 var parms = new Dictionary<string, string> { { "speakerId", "11" } }; --> <a asp-route="speakerevals" asp-all-route-data="parms">Speaker Evaluations</a> <!-- href="/Speaker/Evaluations?speakerId=11" asp-route 使用命名的路由 asp-all-route-data 使用字典作为 QueryString 拼接 --> <a asp-controller="Speaker" asp-action="Evaluations" asp-fragment="SpeakerEvaluations">Speaker Evaluations</a> <!-- 追加哈希字符 # 在路径末尾 href="/Speaker/Evaluations#SpeakerEvaluations" --> <a asp-area="Blogs" asp-controller="Home" asp-action="AboutBlog">About Blog</a> <!-- <a href="/Blogs/Home/AboutBlog">About Blog</a> -->
使用 JQuery
<input type="file" class="interval" id="fileInput" multiple name="file" />
<input id="button" type="button" value="Click" />
@section scripts{
<script type="text/javascript">
// 页面加载完成时
$(document).ready(function () {
// 绑定按钮事件(文件上传)
$("#button").on("click", (function (event) {
var files = $("#fileInput").get(0).files;
var fileData = new FormData();
for (var i = 0; i < files.length; i++)
fileData.append("fileInput", files[i]);
// 添加 ajax Post 访问
$.ajax({
type: "POST",
url: "https://localhost:7166/api/Questions/ParseOlderJson",
dataType: "json",
contentType: false, // Not to set any content header
processData: false, // Not to process data
data: fileData,
success: function (result, status, xhr) {
$("#fileInput").val("");
}
}).fail(err => console.log(err));
}));
});
</script>
}