将模型添加到 ASP.NET Core MVC 应用Add a model to an ASP.NET Core MVC app

作者:Rick AndersonTom DykstraBy Rick Anderson and Tom Dykstra

在本部分中将添加用于管理数据库中的电影的类。In this section, you add classes for managing movies in a database. 这些类将是 MVC 应用的“Model”部分 。These classes will be the "Model" part of the MVC app.

可以结合 Entity Framework Core (EF Core) 使用这些类来处理数据库。You use these classes with Entity Framework Core (EF Core) to work with a database. EF Core 是对象关系映射 (ORM) 框架,可以简化需要编写的数据访问代码。EF Core is an object-relational mapping (ORM) framework that simplifies the data access code that you have to write.

要创建的模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系 。The model classes you create are known as POCO classes (from Plain Old CLR Objects) because they don't have any dependency on EF Core. 它们只定义将存储在数据库中的数据的属性。They just define the properties of the data that will be stored in the database.

在本教程中,首先要编写模型类,然后 EF Core 将创建数据库。In this tutorial, you write the model classes first, and EF Core creates the database. 有一种备选方法(此处未介绍):从现有数据库生成模型类。An alternate approach not covered here is to generate model classes from an existing database. 有关此方法的信息,请参阅 ASP.NET Core - Existing Database(ASP.NET Core - 现有数据库)。For information about that approach, see ASP.NET Core - Existing Database.

添加数据模型类Add a data model class

右键单击 Models 文件夹,然后单击“添加” > “类” 。Right-click the Models folder > Add > Class. 将类命名“Movie” 。Name the class Movie.

Movie 类添加以下属性:Add the following properties to the Movie class:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

Movie 类包含:The Movie class contains:

  • 数据库需要 Id 字段以获取主键。The Id field which is required by the database for the primary key.

  • [DataType(DataType.Date)]DataType 属性指定数据的类型 (Date)。[DataType(DataType.Date)]: The DataType attribute specifies the type of the data (Date). 通过此特性:With this attribute:

    • 用户无需在数据字段中输入时间信息。The user is not required to enter time information in the date field.
    • 仅显示日期,而非时间信息。Only the date is displayed, not time information.

DataAnnotations 会在后续教程中介绍。DataAnnotations are covered in a later tutorial.

搭建“电影”模型的基架Scaffold the movie model

在此部分,将搭建“电影”模型的基架。In this section, the movie model is scaffolded. 确切地说,基架工具将生成页面,用于对“电影”模型执行创建、读取、更新和删除 (CRUD) 操作。That is, the scaffolding tool produces pages for Create, Read, Update, and Delete (CRUD) operations for the movie model.

在解决方案资源管理器中,右键单击“Controllers”文件夹 >“添加”>“新搭建基架的项目” 。In Solution Explorer, right-click the Controllers folder > Add > New Scaffolded Item.

上述步骤的视图

在“添加基架”对话框中,选择“包含视图的 MVC 控制器(使用 Entity Framework)”>“添加” 。In the Add Scaffold dialog, select MVC Controller with views, using Entity Framework > Add.

“添加基架”对话框

填写“添加控制器”对话框: Complete the Add Controller dialog:

  • 模型类 :Movie (MvcMovie.Models) Model class: Movie (MvcMovie.Models)
  • 数据上下文类 :选择 + 图标并添加默认的 MvcMovie.Models.MvcMovieContext Data context class: Select the + icon and add the default MvcMovie.Models.MvcMovieContext

“添加数据”上下文

  • 视图 :将每个选项保持为默认选中状态Views: Keep the default of each option checked
  • 控制器名称: 保留默认的 MoviesController Controller name: Keep the default MoviesController
  • 选择“添加” Select Add

“添加控制器”对话框

Visual Studio 将创建:Visual Studio creates:

  • Entity Framework Core 数据库上下文类 (Data/MvcMovieContext.cs) An Entity Framework Core database context class (Data/MvcMovieContext.cs)
  • 电影控制器 (Controllers/MoviesController.cs) A movies controller (Controllers/MoviesController.cs)
  • “创建”、“删除”、“详细信息”、“编辑”和“索引”页面的 Razor 视图文件 (Views/Movies/*.cshtml) Razor view files for Create, Delete, Details, Edit, and Index pages (Views/Movies/*.cshtml)

自动创建数据库上下文和 CRUD(创建、读取、更新和删除)操作方法和视图的过程称为“搭建基架” 。The automatic creation of the database context and CRUD (create, read, update, and delete) action methods and views is known as scaffolding.

如果运行应用并单击“Mvc 电影”链接,则会出现以下类似的错误 :If you run the app and click on the Mvc Movie link, you get an error similar to the following:

An unhandled exception occurred while processing the request.

SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.

System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString

你需要创建数据库,并且使用 EF Core 迁移功能来执行此操作。You need to create the database, and you use the EF Core Migrations feature to do that. 通过迁移可创建与数据模型匹配的数据库,并在数据模型更改时更新数据库架构。Migrations lets you create a database that matches your data model and update the database schema when your data model changes.

初始迁移Initial migration

在本节中,将完成以下任务:In this section, the following tasks are completed:

  • 添加初始迁移。Add an initial migration.
  • 使用初始迁移来更新数据库。Update the database with the initial migration.
  1. 从“工具” 菜单中,选择“NuGet 包管理器” > “包管理器控制台” (PMC)。From the Tools menu, select NuGet Package Manager > Package Manager Console (PMC).

    PMC 菜单

  2. 在 PMC 中,输入以下命令:In the PMC, enter the following commands:

    Add-Migration Initial
    Update-Database
    

    Add-Migration 命令生成用于创建初始数据库架构的代码。The Add-Migration command generates code to create the initial database schema.

    数据库架构基于在 MvcMovieContext 类中指定的模型。The database schema is based on the model specified in the MvcMovieContext class. Initial 参数是迁移名称。The Initial argument is the migration name. 可以使用任何名称,但是按照惯例,会使用可说明迁移的名称。Any name can be used, but by convention, a name that describes the migration is used. 有关更多信息,请参见教程:使用迁移功能 - ASP.NET MVC 和 EF CoreFor more information, see 教程:使用迁移功能 - ASP.NET MVC 和 EF Core.

    Update-Database 命令在用于创建数据库的 Migrations/{time-stamp}_InitialCreate.cs 文件中运行 Up 方法 。The Update-Database command runs the Up method in the Migrations/{time-stamp}_InitialCreate.cs file, which creates the database.

检查通过依赖关系注入注册的上下文Examine the context registered with dependency injection

ASP.NET Core 通过依赖关系注入 (DI) 生成。ASP.NET Core is built with dependency injection (DI). 服务(例如 EF Core 数据库上下文)在应用程序启动期间通过 DI 注册。Services (such as the EF Core DB context) are registered with DI during application startup. 需要这些服务(如 Razor 页面)的组件通过构造函数提供相应服务。Components that require these services (such as Razor Pages) are provided these services via constructor parameters. 本教程的后续部分介绍了用于获取 DB 上下文实例的构造函数代码。The constructor code that gets a DB context instance is shown later in the tutorial.

基架工具自动创建数据库上下文并将其注册到 DI 容器。The scaffolding tool automatically created a DB context and registered it with the DI container.

我们来研究以下 Startup.ConfigureServices 方法。Examine the following Startup.ConfigureServices method. 基架添加了突出显示的行:The highlighted line was added by the scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies 
        // is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddDbContext<MvcMovieContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

MvcMovieContextMovie 模型协调 EF Core 功能(创建、读取、更新、删除等)。The MvcMovieContext coordinates EF Core functionality (Create, Read, Update, Delete, etc.) for the Movie model. 数据上下文 (MvcMovieContext) 派生自 Microsoft.EntityFrameworkCore.DbContextThe data context (MvcMovieContext) is derived from Microsoft.EntityFrameworkCore.DbContext. 数据上下文指定数据模型中包含哪些实体:The data context specifies which entities are included in the data model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace MvcMovie.Models
{
    public class MvcMovieContext : DbContext
    {
        public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
            : base(options)
        {
        }

        public DbSet<MvcMovie.Models.Movie> Movie { get; set; }
    }
}

前面的代码为实体集创建 DbSet<Movie> 属性。The preceding code creates a DbSet<Movie> property for the entity set. 在实体框架术语中,实体集通常与数据表相对应。In Entity Framework terminology, an entity set typically corresponds to a database table. 实体对应表中的行。An entity corresponds to a row in the table.

通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文。The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. 进行本地开发时, ASP.NET Core 配置系统appsettings.json 文件中读取数据库连接字符串。For local development, the ASP.NET Core configuration system reads the connection string from the appsettings.json file.

测试应用Test the app

  • 运行应用并将 /Movies 追加到浏览器中的 URL (http://localhost:port/movies)。Run the app and append /Movies to the URL in the browser (http://localhost:port/movies).

如果收到如下所示数据库异常:If you get a database exception similar to the following:

SqlException: Cannot open database "MvcMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.

缺少迁移步骤You missed the migrations step.

  • 测试“创建” 链接。Test the Create link. 输入并提交数据。Enter and submit data.

    备注

    可能无法在 Price 字段中输入十进制逗号。You may not be able to enter decimal commas in the Price field. 若要使 jQuery 验证支持使用逗号(“,”)表示小数点的非英语区域设置,以及支持非美国英语日期格式,应用必须进行全球化。To support jQuery validation for non-English locales that use a comma (",") for a decimal point and for non US-English date formats, the app must be globalized. 有关全球化的说明,请参阅此 GitHub 问题For globalization instructions, see this GitHub issue.

  • 测试“编辑” 、“详细信息” 和“删除” 链接。Test the Edit, Details, and Delete links.

检查 Startup 类:Examine the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies 
        // is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddDbContext<MvcMovieContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

上面突出显示的代码显示了要添加到依赖关系注入容器的电影数据库上下文:The preceding highlighted code shows the movie database context being added to the Dependency Injection container:

  • services.AddDbContext<MvcMovieContext>(options => 指定要使用的数据库和连接字符串。services.AddDbContext<MvcMovieContext>(options => specifies the database to use and the connection string.
  • =>lambda 运算符=> is a lambda operator

打开 Controllers/MoviesController.cs 文件并检查构造函数 :Open the Controllers/MoviesController.cs file and examine the constructor:

public class MoviesController : Controller
{
    private readonly MvcMovieContext _context;

    public MoviesController(MvcMovieContext context)
    {
        _context = context;
    }

构造函数使用依赖关系注入将数据库上下文 (MvcMovieContext) 注入到控制器中。The constructor uses Dependency Injection to inject the database context (MvcMovieContext) into the controller. 数据库上下文将在控制器中的每个 CRUD 方法中使用。The database context is used in each of the CRUD methods in the controller.

强类型模型和 @model 关键词Strongly typed models and the @model keyword

在本教程之前的内容中,已经介绍了控制器如何使用 ViewData 字典将数据或对象传递给视图。Earlier in this tutorial, you saw how a controller can pass data or objects to a view using the ViewData dictionary. ViewData 字典是一个动态对象,提供了将信息传递给视图的方便的后期绑定方法。The ViewData dictionary is a dynamic object that provides a convenient late-bound way to pass information to a view.

MVC 还提供将强类型模型对象传递给视图的功能。MVC also provides the ability to pass strongly typed model objects to a view. 凭借此强类型方法可更好地对代码进行编译时检查。This strongly typed approach enables better compile time checking of your code. 基架机制在创建方法和视图时,通过 MoviesController 类和视图使用了此方法(即传递强类型模型)。The scaffolding mechanism used this approach (that is, passing a strongly typed model) with the MoviesController class and views when it created the methods and views.

检查 Controllers/MoviesController.cs 文件中生成的 Details 方法 :Examine the generated Details method in the Controllers/MoviesController.cs file:

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie
        .FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

id 参数通常作为路由数据传递。The id parameter is generally passed as route data. 例如 https://localhost:5001/movies/details/1 的设置如下:For example https://localhost:5001/movies/details/1 sets:

  • 控制器被设置为 movies 控制器(第一个 URL 段)。The controller to the movies controller (the first URL segment).
  • 操作被设置为 details(第二个 URL 段)。The action to details (the second URL segment).
  • ID 被设置为 1(最后一个 URL 段)。The id to 1 (the last URL segment).

还可以使用查询字符串传入 id,如下所示:You can also pass in the id with a query string as follows:

https://localhost:5001/movies/details?id=1

在未提供 ID 值的情况下,id 参数可定义为可以为 null 的类型 (int?)。The id parameter is defined as a nullable type (int?) in case an ID value isn't provided.

Lambda 表达式会被传入 FirstOrDefaultAsync 以选择与路由数据或查询字符串值相匹配的电影实体。A lambda expression is passed in to FirstOrDefaultAsync to select movie entities that match the route data or query string value.

var movie = await _context.Movie
    .FirstOrDefaultAsync(m => m.Id == id);

如果找到了电影,Movie 模型的实例则会被传递到 Details 视图:If a movie is found, an instance of the Movie model is passed to the Details view:

return View(movie);

检查 Views/Movies/Details.cshtml 文件的内容 :Examine the contents of the Views/Movies/Details.cshtml file:

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Movie</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

通过将 @model 语句包括在视图文件的顶端,可以指定视图期望的对象类型。By including a @model statement at the top of the view file, you can specify the type of object that the view expects. 创建电影控制器时,会自动在 Details.cshtml 文件的顶端包括以下 @model 语句 :When you created the movie controller, the following @model statement was automatically included at the top of the Details.cshtml file:

@model MvcMovie.Models.Movie

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的电影。This @model directive allows you to access the movie that the controller passed to the view by using a Model object that's strongly typed. 例如,在 Details.cshtml 视图中,代码通过强类型的 Model 对象将每个电影字段传递给 DisplayNameForDisplayForHTML 帮助程序 。For example, in the Details.cshtml view, the code passes each movie field to the DisplayNameFor and DisplayFor HTML Helpers with the strongly typed Model object. CreateEdit 方法以及视图也传递一个 Movie 模型对象。The Create and Edit methods and views also pass a Movie model object.

检查电影控制器中的 Index.cshtml 视图和 Index 方法 。Examine the Index.cshtml view and the Index method in the Movies controller. 请注意代码在调用 View 方法时是如何创建 List 对象的。Notice how the code creates a List object when it calls the View method. 代码将此 Movies 列表从 Index 操作方法传递给视图:The code passes this Movies list from the Index action method to the view:

// GET: Movies
public async Task<IActionResult> Index()
{
    return View(await _context.Movie.ToListAsync());
}

创建电影控制器时,基架会自动在 Index.cshtml 文件的顶端包含以下 @model 语句 :When you created the movies controller, scaffolding automatically included the following @model statement at the top of the Index.cshtml file:

@model IEnumerable<MvcMovie.Models.Movie>

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的电影列表。The @model directive allows you to access the list of movies that the controller passed to the view by using a Model object that's strongly typed. 例如,在 Index.cshtml 视图中,代码使用 foreach 语句通过强类型 Model 对象对电影进行循环遍历 :For example, in the Index.cshtml view, the code loops through the movies with a foreach statement over the strongly typed Model object:

@model IEnumerable<MvcMovie.Models.Movie>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

因为 Model 对象为强类型(作为 IEnumerable<Movie> 对象),因此循环中的每个项都被类型化为 MovieBecause the Model object is strongly typed (as an IEnumerable<Movie> object), each item in the loop is typed as Movie. 除其他优点之外,这意味着可对代码进行编译时检查:Among other benefits, this means that you get compile time checking of the code:

其他资源Additional resources