Appearance
ASP.NET Core MVC 入门
创建项目
sh
$ dotnet new console -o MvcSamples- 默认项目结构

(图片来源:自己截的)
添加控制器
- 新建Controllers/HelloWorldController.cs:
c#
using Microsoft.AspNetCore.Mvc;
namespace MvcSamples.Controllers;
public class HelloWorldController : Controller
{
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}MVC 所用的默认 URL 路由逻辑使用如下格式来确定调用的代码:
/[Controller]/[ActionName]/[Parameters]
路由格式是在 Program.cs 文件中设置的。
c#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");如果浏览到应用且不提供任何 URL 段,它将默认为上面突出显示的模板行中指定的“Home”控制器和“Index”方法。
修改代码,将一些参数信息从 URL 传递到控制器。 例如 /HelloWorld/Welcome?name=Rick&numtimes=4。
c#
// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}前面的代码:
- 使用 C# 的可选参数功能,当没有为numTimes参数传递值时,指示该参数默认为 1。
- 使用
HtmlEncoder.Default.Encode防止恶意输入(例如通过 JavaScript)损害应用。 - 在
$"Hello {name}, NumTimes is: {numTimes}"中使用插值字符串。
添加视图
- 新建Views/HelloWorld/Index.cshtml:
c#
@{
ViewData["Title"] = "Hello, World";
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - Mvc Samples</title>
</head>
<body>
<header>
<nav>
<!-- 导航菜单 -->
</nav>
</header>
<main>
<h2>Hello, World</h2>
<p>Hello from our View Template!</p>
</main>
<footer>
<!-- 页脚内容 -->
</footer>
</body>
</html>- 在
HelloWorldController类中,将Index方法替换为以下代码:
c#
public IActionResult Index()
{
return View();
}前面的代码:
- 调用该控制器的 View 方法。
- 使用视图模板生成 HTML 响应。
导航到 https://localhost:{PORT}/HelloWorld:
Index中的HelloWorldController方法运行return View();语句,指定此方法应使用视图模板文件来呈现对浏览器的响应。由于未指定视图模板文件名称,因此 MVC 默认使用默认视图文件。 如果未指定视图文件名称,则返回默认视图。 默认视图与操作方法的名称相同,在本例中为
Index。 使用视图模板 /Views/HelloWorld/Index.cshtml。
MVC中的布局
在 ASP.NET Core MVC 中,布局(Layout) 相当于传统 Web 开发中的母版页,它提供了一种在多个视图之间共享通用页面结构的机制。
按照约定,MVC中的默认布局文件为 Views/Shared/_Layout.cshtml。
c#
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - Mvc Samples</title>
<!-- 定义可选的或必需的内容区块 -->
@RenderSection("Styles", required: false)
</head>
<body>
<header>
<nav>
<!-- 导航菜单 -->
</nav>
</header>
<main>
<!-- 标记子视图内容插入的位置 -->
@RenderBody()
</main>
<footer>
<!-- 页脚内容 -->
</footer>
<!-- 定义可选的或必需的内容区块 -->
@RenderSection("Scripts", required: false)
</body>
</html>@RenderBody()
占位符:标记子视图内容插入的位置
每个布局中必须有一个且只能有一个 @RenderBody()
@RenderSection()
定义可选的或必需的内容区块:
c#
<!-- 必需区块 -->
@RenderSection("Styles", required: true)
<!-- 可选区块 -->
@RenderSection("Scripts", required: false)视图使用布局
- 显式指定布局
c#
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<!-- 或者 -->
@{
Layout = "_Layout"; // 使用视图引擎查找
}- 不使用布局
c#
@{
Layout = null;
}TIP
- 修改Views/HelloWorld/Index.cshtml
c#
@{
Layout = "_Layout";
ViewData["Title"] = "Hello, World";
}
<h2>Hello, World</h2>
<p>Hello from our View Template!</p>区块(Sections)的使用
在视图中定义区块内容:
c#
@section Styles {
<style>
.custom-style { color: red; }
</style>
}
<!-- 主视图内容 -->
<div>这是页面主要内容</div>
@section Scripts {
<script>
console.log('页面加载完成');
</script>
}TIP
- 修改Views/HelloWorld/Index.cshtml
c#
@{
Layout = "_Layout";
ViewData["Title"] = "Hello, World";
}
<h2>Hello, World</h2>
<p>Hello from our View Template!</p>
@section Styles {
<style>
h2 {
color: red;
}
</style>
<script>
alert('首页加载完成');
</script>
}在每个视图之前运行代码
需要在每个视图或页面之前运行的代码应置于 _ViewStart.cshtml 文件中。
按照约定,MVC中的 _ViewStart.cshtml 文件位于 Views/_ViewStart.cshtml。常用于设置默认布局:
c#
@{
Layout = "_Layout";
}TIP
- 新建Views/_ViewStart.cshtml
c#
@{
Layout = "_Layout";
}- 修改Views/HelloWorld/Index.cshtml
c#
@{
ViewData["Title"] = "Hello, World";
}
<h2>Hello, World</h2>
<p>Hello from our View Template!</p>
@section Styles {
<style>
h2 {
color: red;
}
</style>
<script>
alert('首页加载完成');
</script>
}为每个视图导入共享指令
视图和页面可以使用 Razor 指令来导入命名空间并使用依赖项注入。 可在一个共同的 _ViewImports.cshtml 文件中指定由许多视图共享的指令。
按照约定,MVC中的 _ViewImports.cshtml 文件位于 Views/_ViewImports.cshtml。
c#
@* 引入命名空间,使视图可以直接使用该命名空间下的类 *@
@using MvcSamples
@using MvcSamples.Models
@* 注册Tag Helper,使自定义标签助手在视图中可用 *@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers将数据从控制器传递给视图
- 在
HelloWorldController类中,将Welcome方法替换为以下代码:
c#
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}- 新建一个名为Views/HelloWorld/Welcome.cshtml的
Welcome视图模板:
c#
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>添加模型
添加数据模型类
- 新建Models/Product.cs:
c#
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcSamples.Models;
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
[DataType(DataType.Date)]
public DateTime CreatedDate { get; set; }
public decimal Price { get; set; }
}使用 aspnet-codegenerator 生成 CRUD 控制器和视图(可选)
sh
$ dotnet tool uninstall --global dotnet-aspnet-codegenerator
$ dotnet tool install --global dotnet-aspnet-codegenerator
$ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
$ dotnet add package Microsoft.EntityFrameworkCore.Tools
$ dotnet aspnet-codegenerator controller -name ProductController -m Product -dc MvcSamples.Data.ProductDbContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries --databaseProvider sqlite
Building project ...
Finding the generator 'controller'...
Running the generator 'controller'...
Minimal hosting scenario!
Generating a new DbContext class 'MvcSamples.Data.ProductDbContext'
Attempting to compile the application in memory with the added DbContext.
Attempting to figure out the EntityFramework metadata for the model and DbContext: 'Product'
Using database provider 'Microsoft.EntityFrameworkCore.Sqlite'!
Added DbContext : '\Data\ProductDbContext.cs'
Added Controller : '\Controllers\ProductController.cs'.
Added View : \Views\Product\Create.cshtml
Added View : \Views\Product\Edit.cshtml安装 Entity Framework Core
sh
$ dotnet add package Microsoft.EntityFrameworkCore.Sqlite- 修改Program.cs
c#
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("ProductDbContext") ?? throw new InvalidOperationException("Connection string 'ProductDbContext' not found.")));- 修改appsettings.json
json
"ConnectionStrings": {
"ProductDbContext": "Data Source=Product.db"
}使用 dotnet-ef 生成数据库(可选)
sh
$ dotnet tool uninstall --global dotnet-ef
$ dotnet tool install --global dotnet-ef
$ dotnet add package Microsoft.EntityFrameworkCore.Design
$ dotnet ef migrations add InitialCreate
$ dotnet ef database update添加新字段
- 修改模型
在 Product.cs 中添加 Type 属性。
c#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcSamples.Models;
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
[DataType(DataType.Date)]
public DateTime CreatedDate { get; set; }
public decimal Price { get; set; }
[DataType(DataType.Text)]
public ProductTypeEnum Type { get; set; }
}
public enum ProductTypeEnum
{
Foods, Clothes, Books
}- 修改控制器
在 ProductController.cs 中。修改 POST 请求类型的 Edit 和 Create 操作方法的 [Bind] 特性,以包括新属性Type:
c#
[Bind("Id,Name,Description,CreatedDate,Price,Type")]- 修改视图
在 /Views/Product/ 目录下的所有文件中添加 Type 字段。还需要在 Create.cshtml 和 Edit.cshtml 中填充 Type 下拉列表数据。
c#
<div class="form-group">
<label asp-for="Type" class="control-label"></label>
<select asp-for="Type" asp-items="@Html.GetEnumSelectList<ProductTypeEnum>()">
<option value="">All</option>
</select>
<span asp-validation-for="Type" class="text-danger"></span>
</div>- 使用
dotnet-ef重新生成数据库(可选)
删除 Migrations 文件夹和数据库文件,然后运行以下 .NET CLI 命令:
sh
$ dotnet ef migrations add InitialCreate
$ dotnet ef database update添加搜索
添加模糊搜索
- 更新 Controllers/ProductController.cs 中的
Index方法:
c#
public async Task<IActionResult> Index(string productName)
{
var products = from product in _context.Product select product;
if (!String.IsNullOrEmpty(productName))
{
products = products.Where(p => p.Name.Contains(productName));
ViewData["productName"] = productName;
}
return View(await products.ToListAsync());
}- 更新 Views/Product/Index.cshtml 文件:
c#
<form asp-controller="Product" asp-action="Index" method="get">
<p>
<label>Name: <input type="text" name="productName" asp-for="@ViewData["productName"]" /></label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
...添加下拉列表搜索
- 更新 Controllers/ProductController.cs 中的
Index方法:
c#
public async Task<IActionResult> Index(string productName, ProductTypeEnum? productType)
{
var products = from product in _context.Product select product;
if (!String.IsNullOrEmpty(productName))
{
products = products.Where(p => p.Name.Contains(productName));
ViewData["productName"] = productName;
}
if (productType is not null)
{
products = products.Where(p => p.Type == productType);
}
// 为了视图能回显查询时选中的下拉参数
ViewData["Types"] = new SelectList(Enum.GetValues<ProductTypeEnum>(), selectedValue: productType);
return View(await products.ToListAsync());
}- 更新 Views/Product/Index.cshtml 文件:
c#
<form asp-controller="Product" asp-action="Index" method="get">
<p>
<label>Name: <input type="text" name="productName" asp-for="@ViewData["productName"]" /></label>
<label>Type: <select name="productType" asp-items="@(ViewData["Types"] as SelectList)">
<option value="">All</option>
</select>
</label>
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
...