Skip to content

ASP.NET Core MVC 入门

创建项目

sh
$ dotnet new console -o MvcSamples
  • 默认项目结构

aps.net core mvc project

(图片来源:自己截的)

添加控制器

  • 新建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.cshtmlWelcome视图模板:
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 请求类型的 EditCreate 操作方法的 [Bind] 特性,以包括新属性Type

c#
[Bind("Id,Name,Description,CreatedDate,Price,Type")]
  • 修改视图

/Views/Product/ 目录下的所有文件中添加 Type 字段。还需要在 Create.cshtmlEdit.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">
...

Last updated:

Released under the MIT License.