65.9K
CodeProject 正在变化。 阅读更多。
Home

Entity Framework Core 入门:使用 Web API 和 Code First 开发构建 ASP.NET Core 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (12投票s)

2017 年 12 月 4 日

CPOL

19分钟阅读

viewsIcon

50649

downloadIcon

1951

本文演示了如何使用 Entity Framework Core Code-First 开发以及 Web API 和 MVC 来构建 ASP.NET Core 应用程序。

引言

在前一篇文章中,我们了解了 Entity Framework 的概览以及如何使用 Entity Framework 的 Database-First 开发快速构建 Web 应用程序。

这一次,我们将构建一个简单但真实的 ASP.NET Core 应用程序,并展示 Entity Framework Core 的功能。但在我们深入研究之前,让我们快速回顾一下 Code-First 和 Database-First 设计工作流程的总体差异和主要优势,然后我们将决定在项目中要使用哪种。

设计工作流程

提醒一下,Entity Framework Core 支持两种主要的设计工作流程:Code-First 方法,即您创建类(POCO 实体)并从中生成新数据库。Database-First 方法允许您使用现有数据库并根据数据库架构生成类。下表显示了每种设计工作流程如何工作的总体流程。

Code First 数据库优先 (Database First)

图 1:EF Core 设计工作流程

Code First(新数据库)

  • 如果您不了解数据库的整体情况,这是一个不错的选择,因为您可以直接更新您的普通旧类对象(POCO)实体,然后让 EF 将您的更改同步到您的数据库。换句话说,您可以轻松添加或删除类中定义的特性,而无需担心使用 Migrations 同步数据库。
  • 您无需担心数据库,因为 EF 会为您处理创建。本质上,数据库只是一个不包含逻辑的存储介质。
  • 您将完全控制代码。您只需定义和创建 POCO 实体,然后让 EF 为您生成相应的数据库。缺点是,如果您手动更改数据库中的任何内容,您可能会丢失它们,因为您的代码定义了数据库。
  • 由于没有自动生成的代码,因此易于修改和维护。

Database First(现有数据库)

  • 如果您擅长数据库,或者您有一个由 DBA 设计的数据库,或者您已经有一个已开发的现有数据库,这是一个不错的选择。
  • 您无需担心创建 POCO 对象,因为 EF 会根据您现有的数据库为您生成它们。
  • 如果您希望 POCO 实体具有其他特性,您可以创建部分类。
  • 您可以手动修改您的数据库,因为数据库定义了您的 POCO 实体(领域模型)。
  • 提供更灵活的数据库配置控制。

总结

当您拥有一个单独开发的现有数据库时,或者当时间对您很重要时,您可以选择数据库优先的路线。数据库优先使我们能够更快地构建应用程序,因为您只需点击几下即可生成领域模型。对于大型应用程序或长期客户项目,代码优先为我们提供了创建最高效程序所需的控制,同时还提供了版本控制数据库的保护和一致性,同时减少了膨胀。

做出选择

通过比较这两种方法的差异,我们可以清楚地看到,不存在绝对更好的方法:相反,我们可以说每种项目场景都可能有一种最合适的方法。

现在,就我们将要构建的应用程序而言,考虑到我们还没有数据库,并且我们正在构建一个灵活、可变的小型数据结构,采用 Code-First 方法可能是一个不错的选择。这就是我们将要做的事情:使用 Entity Framework Core 的 Code-First 方法从头开始构建一个 ASP.NET Core 应用程序。

ASP.NET Core 革命

要总结过去一年 ASP.NET 世界中发生的事情并非易事:简而言之,我们可以说我们无疑正面临自 .NET Framework 问世以来最重要的系列变革。ASP.NET Core 是对 ASP.NET 的完全重写,它将所有之前的 Web 应用程序技术(如 MVC、Web API 和 Razor Pages)统一到一个名为 MVC6 的单一编程模块中。新框架引入了一个功能齐全的跨平台组件,也称为 .NET Core,配备了一个全新的开源 .NET 编译器平台(目前称为 Roslyn),以及一个名为 CoreCLR 的跨平台运行时。


ASP.NET Core 是一个全新的开源、跨平台的现代云 Web 应用程序构建框架。它从头开始构建,为部署在云端或本地服务器上的 Web 应用程序提供了一个优化的开发框架。此外,它经过重新设计,使 ASP.NET 更精简、模块化(因此您可以只添加应用程序所需的功能)、跨平台(因此您可以在 Windows、Mac 或 Linux 上轻松开发和运行应用程序,这非常棒)且云优化(因此您可以跨云部署和调试应用程序)。

三个玩家,一个目标

我们的主要目标是使用三种尖端技术构建一个数据驱动的 Web 应用程序:ASP.NET Core MVC、Web API 和 Entity Framework Core。

ASP.NET Web API 是一个用于构建 HTTP 服务的框架,是构建 .NET Framework 上 RESTful 应用程序的理想平台。

ASP.NET Core MVC 是一种基于模式的动态网站构建方法,它实现了关注点的清晰分离,让您可以完全控制标记或 HTML。

我们将使用 ASP.NET Core 构建 REST API(Web API)以及我们的前端框架(MVC)来生成页面或视图(也称为用户界面)。另一方面,我们将使用 Entity Framework Core 作为我们的数据访问机制来处理数据库数据。下图说明了每种技术如何相互交互。

图 2:总体流程

上图说明了用户访问数据库信息时的过程。当用户请求一个页面时,它会显示视图/UI(MVC)。当用户想要查看某些数据时,MVC 应用程序将与 Web API 服务通信以处理它。然后,Web API 将与 EF Core 通信以处理实际请求(CRUD 操作),并在必要时更新数据库。

设置 ASP.NET Core Web API 项目

现在我们已经了解了 ASP.NET Core 和 EF Core 的总体概述,是时候动手构建一个简单但数据驱动的 Web 应用程序了,使用 ASP.NET Core 和 Entity Framework Core。让我们先创建 Web API 项目。

启动 Visual Studio 2017,创建一个新的 ASP.NET Core Web 应用程序项目。为此,请选择 文件 > 新建 > 项目。在“新建项目”对话框中,选择 Visual C# > Web > ASP.NET Core Web 应用程序(.NET Core),如下图所示。

图 3:创建新的 ASP.NET Core 项目

在对话框中,输入您的项目名称,然后单击“确定”按钮进入下一步。请注意,在此特定演示中,我们将项目命名为“StudentApplication.API”。然后单击 Web API 模板,如下图所示。

图 4:ASP.NET Core Web API 模板

在新的 ASP.NET Core 项目模板对话框中,您现在可以使用屏幕顶部的组合框选择使用 ASP.NET Core 1.0 或 1.1。在撰写本文时,默认模板是 ASP.NET Core 1.1。此外,您可以使用屏幕左下角的复选框选择为项目添加容器支持。此选项将添加

  • 为构建容器镜像而创建的 Docker 文件
  • 一个 Docker Compose 项目来定义如何实例化容器
  • 用于容器的多项目/容器调试支持

为简单起见,本次练习我们将省略容器的使用。

现在再次单击“确定”按钮,让 Visual Studio 为您生成默认文件。下图显示了生成的文件。

图 5:Visual Studio 默认生成的文件

让我们快速浏览一下生成的文件。

如果您已经了解 ASP.NET Core 的核心重要更改,则可以跳过此部分;但如果您是 ASP.NET Core 新手,我想强调其中一些更改。如果您之前使用过 ASP.NET 的旧版本,您会注意到新的项目结构完全不同。项目现在包含这些文件:

  • 连接的服务:允许服务提供商创建 Visual Studio 扩展,这些扩展可以添加到项目中,而无需离开 IDE。它还允许您将 ASP.NET Core 应用程序或移动服务连接到 Azure 存储服务。连接的服务会处理所有引用和连接代码,并相应地修改您的配置文件。
  • Controllers:您可以在此处放置所有 Web API 或 MVC 控制器类。请注意,Web API 项目默认会生成 ValuesController.cs 文件。
  • wwwroot:这是一个文件夹,所有静态文件都将放置在此处。这些是 Web 应用程序将直接提供给客户端的资源,包括 HTML、CSS、图像和 JavaScript 文件。
  • appsettings.json:包含应用程序设置。
  • Startup.cs:您可以在此处放置启动和配置代码。
  • Program.cs:您可以在此处初始化应用程序所需的所有服务。

首次运行项目

为了确保我们拥有 Web API 项目所需的一切,让我们尝试构建并运行项目。我们可以通过按 CTRL + F5 键来实现。这将编译、构建并自动启动浏览器窗口。如果您看到以下结果,那么我们可以安全地假定我们的项目已经准备就绪。

图 6:初始运行

设置 Entity Framework Core

让我们将 Entity Framework Core 包安装到我们的项目中。转到 工具 > NuGet 包管理器 -> 包管理器控制台,然后分别运行以下命令:

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

成功安装上述包后,请务必检查您的项目引用,以确保我们在项目中拥有它们。请参见下图。

图 7:Entity Framework Core 包引用

这些包用于提供 Entity Framework Core 迁移。迁移使我们能够根据我们的实体生成数据库、同步和更新表。

创建模型

我们将开始在应用程序的根目录下创建“Models”文件夹。在 *Models* 文件夹内添加一个新类文件,并将其命名为“Student.cs”,然后用以下内容替换默认生成的代码:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace StudentApplication.API.Models
{
    public class Student
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long StudentId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Gender { get; set; }
        public DateTime? DateOfBirth { get; set; }
        public DateTime? DateOfRegistration { get; set; }
        public string PhoneNumber { get; set; }
        public string Email { get; set; }
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
    }
}

上面的代码只是一个包含一些属性的简单类。您可能会注意到我们将 StudentId 属性装饰了 KeyDatabaseGenerated 属性。这是因为我们将把这个类转换为数据库表,并且 StudentId 将作为我们的主键,并具有自动递增的标识。这些属性位于 System.ComponentModel.DataAnnotations 中。有关数据注解的更多详细信息,请参阅: 使用数据注解进行模型验证

创建空数据库

现在是时候创建我们的数据库了。打开 SQL Express Management Studio,然后执行以下命令:

CREATE DATABASE StundentApplication

上面的命令应该会在您的本地创建一个空白数据库。

定义 DBContext

Entity Framework Core Code-First 开发方法要求我们创建一个继承自 DbContext 类的数据库访问上下文类。现在,在 Models 文件夹下添加一个新类。将类命名为“ApplicationContext”,然后复制下面的代码:

using Microsoft.EntityFrameworkCore;

namespace StudentApplication.API.Models
{
    public class ApplicationContext: DbContext
    {
        public ApplicationContext(DbContextOptions opts) : base(opts)
        {
        }

        public DbSet<Student> Students { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
        }
    }
}

上面的代码定义了一个上下文和一个构成我们模型的实体类。DbContext 必须有一个 DbContextOptions 实例才能执行。这可以通过重写 OnConfiguring 来配置,或者通过构造函数参数从外部提供。在上面的示例中,我们选择使用构造函数参数来配置我们的 DbContext

将 DbContext 注册到依赖注入

我们的 ApplicationContext 类将充当一个服务。此服务将在应用程序启动期间注册到 依赖注入 (DI)。这将使我们的 API Controller 或其他服务可以通过构造函数参数或属性访问 ApplicationContext

为了让其他服务能够使用 ApplicationContext,我们将将其注册为服务。要启用该服务,请执行以下步骤:

1. 在 appsettings.json 文件下定义以下数据库连接字符串:

,
  "ConnectionString": {
    "StudentApplicationDB": "Server=SSAI-L0028-HP\\SQLEXPRESS;Database=StundentApplication;Trusted_Connection=True;"
  }

2. 打开 Startup.cs 并声明以下命名空间引用:

using Microsoft.EntityFrameworkCore;
using StudentApplication.API.Models;
using StudentApplication.API.Models.DataManager;
using StudentApplication.API.Models.Repository

3. 在 ConfigureServices 方法中添加以下代码,将我们的 ApplicationContext 注册为服务。

public void ConfigureServices(IServiceCollection services)
{
     // Add framework services.
     services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:StudentApplicationDB"]));
     services.AddSingleton(typeof(IDataRepository<Student, long>), typeof(StudentManager));     
     services.AddMvc();
}

添加迁移

我们的下一步是添加 Code-First 迁移。迁移根据我们的模型自动创建数据库。在 PM 控制台中运行以下命令:

PM> Add-Migration StudentApplication.API.Models.ApplicationContext

命令成功执行后,它应该会生成迁移文件,如下图所示:

图 8:生成的 EF 迁移文件

现在,切换回包管理器控制台并运行以下命令:

PM> update-database

上面的命令应该会根据我们的实体生成数据库表。这是生成数据库表的示例截图:

图 9:生成的表

实现一个简单的数据存储库

我们不希望我们的 API Controller 直接访问我们的 ApplicationContext 服务,而是让其他服务处理我们 ApplicationContextAPI Controller 之间的通信。这样说来,我们将实现一个基本通用存储库来处理我们应用程序中的数据访问。在 Models 下添加一个新文件夹,并将其命名为“Repository”。创建一个新接口并将其命名为“IDataRepository”。更新该文件中的代码,使其看起来类似于下面的代码:

using System.Collections.Generic;

namespace StudentApplication.API.Models.Repository
{
    public interface IDataRepository<TEntity, U> where TEntity : class
    {
        IEnumerable<TEntity> GetAll();
        TEntity Get(U id);
        long Add(TEntity b);
        long Update(U id, TEntity b);
        long Delete(U id);
    }

}

上面的代码定义了我们的 IDataRepository 接口。接口只是方法的骨架,没有实际实现。该接口将被注入到我们的 API Controller 中,因此我们只需要与接口通信,而无需直接与我们存储库的实际实现通信。使用接口的主要好处之一是使我们的代码可重用且易于维护。

创建 DataManager

接下来,我们将创建一个实现 IDataRepository 接口的具体类。在 Models 下添加一个新文件夹,并将其命名为“DataManager”。创建一个新类并将其命名为“StudentManager”。更新该文件中的代码,使其看起来类似于下面的代码:

using StudentApplication.API.Models.Repository;
using System.Collections.Generic;
using System.Linq;

namespace StudentApplication.API.Models.DataManager
{
    public class StudentManager : IDataRepository<Student, long>
    {
        ApplicationContext ctx;
        public StudentManager(ApplicationContext c)
        {
            ctx = c;
        }

        public Student Get(long id)
        {
            var student = ctx.Students.FirstOrDefault(b => b.StudentId == id);
            return student;
        }

        public IEnumerable<Student> GetAll()
        {
            var students = ctx.Students.ToList();
            return students;
        }

        public long Add(Student stundent)
        {
            ctx.Students.Add(stundent);
            long studentID = ctx.SaveChanges();
            return studentID;
        }

        public long Delete(long id)
        {
            int studentID = 0;
            var student = ctx.Students.FirstOrDefault(b => b.StudentId == id);
            if (student != null)
            {
                ctx.Students.Remove(student);
                studentID = ctx.SaveChanges();
            }
            return studentID;
        }

        public long Update(long id, Student item)
        {
            long studentID  = 0;
            var student = ctx.Students.Find(id);
            if (student != null)
            {
                student.FirstName = item.FirstName;
                student.LastName = item.LastName;
                student.Gender = item.Gender;
                student.PhoneNumber = item.PhoneNumber;
                student.Email = item.Email;
                student.DateOfBirth = item.DateOfBirth;
                student.DateOfRegistration = item.DateOfRegistration;
                student.Address1 = item.Address1;
                student.Address2 = item.Address2;
                student.City = item.City;
                student.State = item.State;
                student.Zip = item.Zip;

                studentID = ctx.SaveChanges();
            }
            return studentID;
        }
    }
}

DataManager 类负责我们应用程序的所有数据库操作。此类的目的是将实际数据操作逻辑与我们的 API Controller 分开,并有一个中央类来处理创建、更新、获取和删除(CRUD)操作。

目前,DataManager 类包含五个方法:Get() 方法通过传递 ID 来从数据库获取特定的学生记录。它使用 LINQ 语法查询数据并返回一个 Student 对象。GetAll() 方法获取数据库中的所有学生记录,正如您可能从方法名称中猜到的那样。Add() 方法在数据库中创建一条新的学生记录。Delete() 方法根据 ID 从数据库中删除特定的学生记录。最后,Update() 方法更新数据库中的特定学生记录。以上所有方法都使用 LINQ 从数据库查询数据。

创建 API Controller

现在我们的 DataManager 已经准备就绪,是时候创建 API Controller 并公开一些端点来处理 CRUD 操作了。

继续右键单击 Controllers 文件夹,然后选择 Add > New Item > Web API Controller class。将类命名为“StudentController”,如下图所示,然后单击 Add。

图 10:新的 Web API Controller 类

现在,替换默认生成的代码,使其看起来类似于下面的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using StudentApplication.API.Models;
using StudentApplication.API.Models.Repository;
using StudentApplication.API.Models.DataManager;


namespace StudentApplication.API.Controllers
{
    [Route("api/[controller]")]
    public class StudentController : Controller
    {
        private IDataRepository<Student,long> _iRepo; 
        public StudentController(IDataRepository<Student, long> repo)
        {
            _iRepo = repo;
        }

        // GET: api/values
        [HttpGet]
        public IEnumerable<Student> Get()
        {
            return _iRepo.GetAll();
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public Student Get(int id)
        {
            return _iRepo.Get(id);
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]Student student)
        {
            _iRepo.Add(student);
        }

        // POST api/values
        [HttpPut]
        public void Put([FromBody]Student student)
        {
            _iRepo.Update(student.StudentId,student);
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public long Delete(int id)
        {
            return _iRepo.Delete(id);
        }
    }
}

StudentController 派生自 Controller 基类,并通过使用 Route 属性对其进行装饰,使该类成为 Web API Controller

如果您注意到,我们使用了 Controller Injection 来订阅 IDataRepository 接口。因此,我们不必直接与 StudentManager 类对话,而是简单地与接口对话并使用可用的方法。

StudentController 包含五个(5)个主要方法/端点。第一个 Get() 方法调用 IDataRepository 接口的 GetAll() 方法,并基本上返回数据库中的所有 Student 列表。第二个 Get() 方法根据 ID 返回特定的 Student 对象。请注意,第二个 Get() 方法装饰了 [HttpGet("{id}")],这意味着将“id”附加到路由模板。"{id}"Student 对象 ID 的占位符变量。当调用第二个 Get() 方法时,它将 URL 中的“{id}”值分配给方法的 id 参数。Post() 方法向数据库添加一条新记录。Put() 方法更新数据库中的特定记录。请注意,Post()Put() 都使用 [FromBody] 标签。当参数具有 [FromBody] 时,Web API 使用 Content-Type 标头来选择格式化程序。最后,Delete() 方法从数据库中删除特定记录。

创建 ASP.NET Core MVC 应用程序

此时,我们已经具备了构建此演示 UI 部分所需的所有要求。首先,我们创建了数据库,使用 Entity Framework Core Code-First 设置了数据访问,最后我们已经准备好了 Web API。现在是时候创建一个消耗我们 Web API 端点的 Web 应用程序了。

现在继续创建一个新的 ASP.NET Core 项目。右键单击 Solution,然后选择 Add > New Project > ASP.NET Core Web Application (.NET Core)。您应该能看到类似下图的内容:

 

图 11:新的 ASP.NET Core Web 应用程序项目

为了保持一致性,将项目命名为“StudentApplication.Web”,然后单击 OK。在下一个对话框中,在 ASP.NET Core 1.1 模板下选择“Web Application”,然后单击 OK,让 Visual Studio 为我们生成默认文件。

集成 Newtonsoft.Json

让我们继续在项目中安装 Newtonsoft.Json 包。右键单击项目,然后选择 Manage Nuget Packages。在浏览选项卡中,键入“NewtonSoft”,它应该会显示类似下图的内容:

图 12:添加 Newtonsoft.Json 包

单击“install”将最新版本的包添加到项目中。

稍后我们将在代码中使用 Newtonsoft.Json 来序列化和反序列化 API 请求的对象。

Helper 类

在应用程序的根目录创建一个新类,并将其命名为“Helper.cs”,然后复制下面的代码:

using System;
using System.Net.Http;
using System.Net.Http.Headers;

namespace StudentApplication.Web.Helper
{

    public class StudentAPI
    {
        private string _apiBaseURI = "https://:60883";
        public HttpClient InitializeClient()
        {
            var client = new HttpClient();
            //Passing service base url  
            client.BaseAddress = new Uri(_apiBaseURI);

            client.DefaultRequestHeaders.Clear();
            //Define request data format  
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            return client;

        }
    }

    public class StudentDTO
    {
        public long StudentId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Gender { get; set; }
        public DateTime? DateOfBirth { get; set; }
        public DateTime? DateOfRegistration { get; set; }
        public string PhoneNumber { get; set; }
        public string Email { get; set; }
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
    }
}

Helper.cs 文件包含两个主要类:StudentAPIStudentDTOStudentAPI 类有一个 InitializeClient() 方法来初始化 HttpClient 对象。稍后我们将需要此方法来访问 API 端点。StudentDTO 只是一个普通的类,它模仿了我们 API Controller 中的 Student 类。我们将使用此类作为 API 请求的对象参数。

引用

注意:在本地测试时,您必须将 _apiBaseURI 变量的值更改为您 Web API 项目的托管位置,或者如果您正在本地使用 IISExpress 运行,则更改 Visual Studio 生成的端口号。

控制器

在 Controllers 文件夹下添加一个新控制器,并将其命名为“StudentsController”。替换默认生成的代码,使其看起来类似于下面的代码:

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using StudentApplication.Web.Helper;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace StudentApplication.Web.Controllers
{
    public class StudentsController : Controller
    {
        StudentAPI _studentAPI = new StudentAPI();

        public async Task<IActionResult> Index()
        {
            List<StudentDTO> dto = new List<StudentDTO>();

            HttpClient client = _studentAPI.InitializeClient();

            HttpResponseMessage res = await client.GetAsync("api/student");

            //Checking the response is successful or not which is sent using HttpClient  
            if (res.IsSuccessStatusCode)
            {
                //Storing the response details recieved from web api   
                var result = res.Content.ReadAsStringAsync().Result;

                //Deserializing the response recieved from web api and storing into the Employee list  
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);

            }
            //returning the employee list to view  
            return View(dto);
        }

        // GET: Students/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: Students/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Create([Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] StudentDTO student)
        {
            if (ModelState.IsValid)
            {
                HttpClient client = _studentAPI.InitializeClient();

                var content = new StringContent(JsonConvert.SerializeObject(student), Encoding.UTF8,"application/json");
                HttpResponseMessage res = client.PostAsync("api/student", content).Result;
                if (res.IsSuccessStatusCode)
                {
                    return RedirectToAction("Index");
                }
            }
            return View(student);
        }

        // GET: Students/Edit/1
        public async Task<IActionResult> Edit(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            List<StudentDTO> dto = new List<StudentDTO>();
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = await client.GetAsync("api/student");
 
            if (res.IsSuccessStatusCode)
            { 
                var result = res.Content.ReadAsStringAsync().Result;
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);
            }

            var student = dto.SingleOrDefault(m => m.StudentId == id);
            if (student == null)
            {
                return NotFound();
            }

            return View(student);
        }

        // POST: Students/Edit/1
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Edit(long id, [Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] StudentDTO student)
        {
            if (id != student.StudentId)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                HttpClient client = _studentAPI.InitializeClient();

                var content = new StringContent(JsonConvert.SerializeObject(student), Encoding.UTF8, "application/json");
                HttpResponseMessage res = client.PutAsync("api/student", content).Result;
                if (res.IsSuccessStatusCode)
                {
                    return RedirectToAction("Index");
                }
            }
            return View(student);
        }

        // GET: Students/Delete/1
        public async Task<IActionResult> Delete(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            List<StudentDTO> dto = new List<StudentDTO>();
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = await client.GetAsync("api/student");

            if (res.IsSuccessStatusCode)
            {  
                var result = res.Content.ReadAsStringAsync().Result; 
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);
            }

            var student = dto.SingleOrDefault(m => m.StudentId == id);
            if (student == null)
            {
                return NotFound();
            }

            return View(student);
        }

        // POST: Students/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public IActionResult DeleteConfirmed(long id)
        {
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = client.DeleteAsync($"api/student/{id}").Result;
            if (res.IsSuccessStatusCode)
            {
                return RedirectToAction("Index");
            }

            return NotFound();
        }

    }

   
}

让我们看看上面做了什么。

请注意,我们的 MVC Controller 使用 HttpClient 对象来访问我们的 Web API 端点。我们还使用 Newtonsoft 的 JsonConvert.SerializeObject()JsonConvert.DeserializeObject() 来在两者之间序列化和反序列化数据。

Index() 操作方法获取数据库中的所有 Student 记录。此方法定义为异步的,它返回相应的 Index View 以及 Students 实体列表。

Create() 操作方法仅返回其对应的 View。重载的 Create() 操作方法用 [HttpPost] 属性装饰,这表明该方法只能用于 POST 请求。当模型状态在 POST 时有效时,该方法是我们实际处理向数据库添加新数据的地方。

Edit() 操作方法以 id 作为参数。如果 id 为 null,它返回 NotFound(),否则它将相应的数据返回到 View。重载的 Edit() 操作方法根据 id 更新数据库中的相应记录。

与其他操作一样,Delete() 操作方法以 id 作为参数。如果 id 为 null,它返回 NotFound(),否则它将相应的数据返回到 View。重载的 Delete() 操作方法根据 id 删除数据库中的相应记录。

视图

以下是 Index、Create、Edit 和 Delete 视图的相应标记。

目录

@model IEnumerable<StudentApplication.Web.Helper.StudentDTO>

    @{
        ViewData["Title"] = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }

    <h2>Index</h2>

    <p>
        <a asp-action="Create">Create New</a>
    </p>
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.FirstName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Gender)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.DateOfBirth)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.DateOfRegistration)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.PhoneNumber)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Email)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Address1)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Address2)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.City)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.State)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Zip)
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.FirstName)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.LastName)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Gender)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.DateOfBirth)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.DateOfRegistration)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.PhoneNumber)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Email)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Address1)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Address2)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.City)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.State)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Zip)
                    </td>
                    <td>
                        <a asp-action="Edit" asp-route-id="@item.StudentId">Edit</a> |
                        <a asp-action="Details" asp-route-id="@item.StudentId">Details</a> |
                        <a asp-action="Delete" asp-route-id="@item.StudentId">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>

Create

@model StudentApplication.Web.Helper.StudentDTO

@{
    ViewData["Title"] = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Create</h2>

<form asp-action="Create">
    <div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="FirstName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Gender" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Gender" class="form-control" />
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfBirth" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfBirth" class="form-control" />
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfRegistration" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfRegistration" class="form-control" />
                <span asp-validation-for="DateOfRegistration" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Email" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address1" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address2" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="City" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="State" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Zip" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Zip" class="form-control" />
                <span asp-validation-for="Zip" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

未使用。

@model StudentApplication.Web.Helper.StudentDTO

@{
    ViewData["Title"] = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Edit</h2>

<form asp-action="Edit">
    <div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="StudentId" />
        <div class="form-group">
            <label asp-for="FirstName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Gender" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Gender" class="form-control" />
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfBirth" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfBirth" class="form-control" />
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfRegistration" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfRegistration" class="form-control" />
                <span asp-validation-for="DateOfRegistration" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Email" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address1" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address2" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="City" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="State" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Zip" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Zip" class="form-control" />
                <span asp-validation-for="Zip" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

删除

@model StudentApplication.Web.Helper.StudentDTO

@{
    ViewData["Title"] = "Delete";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="dl-horizontal">
        <input type="hidden" asp-for="StudentId" />
        <dt>
            @Html.DisplayNameFor(model => model.FirstName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.FirstName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.LastName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.LastName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Gender)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Gender)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.DateOfBirth)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.DateOfBirth)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.DateOfRegistration)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.DateOfRegistration)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.PhoneNumber)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.PhoneNumber)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Email)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Email)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Address1)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Address1)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Address2)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Address2)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.City)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.City)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.State)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.State)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Zip)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Zip)
        </dd>
    </dl>

    <form asp-action="Delete">
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            <a asp-action="Index">Back to List</a>
        </div>
    </form>
</div>

上面的所有标记都是强类型视图,因为它们都引用了相同的对象模型,即 StudentApplication.Web.Helper.StudentDTO。它们还使用了 tag-helpers 来定义 HTML 和数据绑定。

测试应用程序

由于此演示由两个项目组成,并且我们的 MVC 应用程序依赖于我们的 Web API 应用程序,因此在测试页面时我们需要使 Web API 可访问。通常,我们将把这两个项目都托管或部署在 IIS Web 服务器上,以便在项目之间进行连接。幸运的是,Visual Studio 2017 的一个很酷的功能是启用多个启动项目。这意味着我们可以在 Visual Studio 中同时运行我们的 Web API 和 MVC 应用程序,并立即进行测试,而无需将它们部署到 IIS。您只需要:

  1. 右键单击 Solution
  2. 选择 Set Startup Projects
  3. 选择 Multiple Startup Projects 单选按钮
  4. 为 Web API 和 MVC 项目都选择“Start”作为操作
  5. 单击 Apply,然后单击 OK

现在使用 CTRL + 5 构建并运行应用程序。在浏览器中,导航到 /students/Create。它应该会显示以下页面。

图 13:Create 视图

填写字段并单击“Create”按钮,它应该会导航到 Index 页面,其中已添加新学生记录,如下图所示:

图 14:Index 视图

当然,Edit 和 Delete 功能也有效 :) 随意下载源代码并尽情玩耍!

摘要

在此系列中,我们学习了使用 Entity Framework Core(新数据库)构建 ASP.NET Core MVC 应用程序。我们还学习了如何创建 Web API Controller 并让 Controller 与接口进行通信。我们还学习了如何实现一个简单的数据访问,使用存储库模式来处理 CRUD 操作。最后,我们学习了如何创建一个基本的 ASP.NET Core MVC 应用程序,该应用程序消耗 Web API 端点以在 UI 中执行 CRUD 操作。

参考文献

  • https://docs.microsoft.com/en-us/aspnet/core/
  • https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
  • https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/new-db
  • https://www.asp.net/web-api

 

© . All rights reserved.