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






4.95/5 (12投票s)
本文演示了如何使用 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
属性装饰了 Key
和 DatabaseGenerated
属性。这是因为我们将把这个类转换为数据库表,并且 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
服务,而是让其他服务处理我们 ApplicationContext
和 API 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
文件包含两个主要类:StudentAPI
和 StudentDTO
。StudentAPI
类有一个 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。您只需要:
- 右键单击 Solution
- 选择 Set Startup Projects
- 选择 Multiple Startup Projects 单选按钮
- 为 Web API 和 MVC 项目都选择“Start”作为操作
- 单击 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