使用ASP.NET MVC 制作一个TODO list 清单
写在前面:
本文原文Getting started - The ASP.NET Core MVC Tutorial (mvc-tutorial.com),看到国内没有相关的简易教程(或者很复杂),所以为了初学者的快速入门以及免FQ,我半机翻半人翻的写这篇文章(肯定不是我英语差),可能有的地方会比较奇怪,如果有条件可以查看原文,还请见谅。
开始:
我们决定使用数据库引擎(MS SQL 服务器,以及用于测试/开发:SQL 服务器快速本地数据库)和数据库框架/ORM(Dapper)。在本文中,我将帮助您开始使用这两种方法,以便您可以继续并了解有关如何使 ASP.NET MVC 项目与数据库一起工作的更多信息。
在接下来的文章中,我们将构建一个完整(但简单)的数据库驱动的 TODO 列表。除了我们现在将要讨论的数据库引擎和框架之外,您还需要一个新的 ASP.NET MVC Core 项目来实现这一点。创建新项目的过程本文将不再赘述。
设置 SQL Server Express (本地数据库)
省流:直接在SQL数据库里新建查询并运行:
CREATE TABLE [dbo].[TodoListItems] ( [Id] INT NOT NULL PRIMARY KEY IDENTITY, [AddDate] DATETIME NOT NULL, [Title] NVARCHAR(200) NOT NULL, [IsDone] BIT NOT NULL DEFAULT 0 )
萌新:微软 SQL 服务器附带了一个易于安装的开发版本,称为 SQL 服务器快速版 (LocalDB)。名称的最后一部分非常有说服力:LocalDB替代方案不是在开发计算机上运行一个完整的SQL Server及其所有服务,这可以从Internet上的任何其他计算机(理论上)访问,而是一个完全修剪的SQL Server版本,只能在您的计算机上本地访问。此外,LocalDB 不是作为持续运行的服务全天候可用,而是在需要时启动。
本地数据库与可视工作室一起安装(除非您主动要求安装程序不要安装),您甚至可以从可视工作室内部控制它。如果尚未看到用于执行此操作的“工具”窗口,只需转到“视图( View)”菜单,然后单击“SQL Server 对象资源管理器”:

现在将出现“SQL Server 对象资源管理器工具”窗口,如下所示:

尝试展开 (localdb) 节点,以便您可以看到数据库等。如果您以前没有使用过 LocalDB(我假设您没有使用过,因为您正在阅读本教程),则“数据库”节点将为空。我们需要为此做点什么,为我们即将到来的TodoList项目创建一个新的数据库:
创建新数据库

在弹出的对话框中,只需为您的数据库指定一个适当的名称 - 我将其命名为TodoList,就像我为此目的创建的Visual Studio项目一样。
添加表
SQL Server 数据库可以包含多个表,这些表是保存实际数据的表。表很像代码中的类 - 它们包含有关特定实体的信息。对于我们的项目,我们需要一个表来保存在我们的TODO列表中找到的所有项目。因此,此表的合适名称是TodoListItems。
添加表与添加数据库一样简单:

您将看到“表设计器”窗口,您可以在其中添加此表所需的行。以下是我的待办事项列表项表的外观:

您可以重新创建我拥有的行,或者如果您想节省时间:只需将省流部分的SQL 复制到设计器的 T-SQL 部分中。
完成后,只需单击“更新”按钮(您可以在上面的屏幕截图顶部看到它)。这将使 Visual Studio 将 SQL 应用于数据库,从而创建新表 - 你将看到它立即出现在 SQL Server 对象资源管理器中。恭喜,您现在有一个数据库,其中包含一个包含行的表 - 换句话说,数据后端已准备就绪,可以使用!
添加Dapper 框架(用EF框架也可,看自己会哪个就用哪个)
数据库就位后,我们现在只需要数据库框架,如前几篇文章所述。借助 NuGet 包系统,在项目中安装第三方库非常容易。这边不
可以从 UI 或使用 NuGet 控制台执行此操作:从“工具”菜单中,选择“NuGet 包管理器 ->包管理器控制台”。控制台将在“工具”窗口中打开,如下所示:

现在键入以下命令,然后按回车键:
Install-Package Dapper
或者点击下面的管理解决方案的NuGet程序包:

这将使 NuGet 管理器将最新版本的 Dapper 库安装到项目中!
在谈论持久性存储(例如像 SQL Server 这样的数据库)时,经常会提到 CRUD 的概念。它代表增删改查(Create, Read, Update, Delete) - 这些是您在使用数据库时将一直执行的操作。但是,Dapper仅包括用于执行读取的方法。这是因为他们希望使库尽可能精简和快速:它将为您执行所有映射,并且您可以从数据库中获取数据,但是如果要执行其他操作之一,则必须手动为其编写SQL。
但别担心!有许多第三方Dapper扩展,可以扩展Dapper的功能来执行这些操作以及许多其他有用的任务。Dapper.Contrib就是一个很好的例子 - 它只是添加了我们需要的CRUD操作,如插入(),更新()和删除()。它可以像Dapper本身一样容易安装,就像Dapper一样,它只是扩展了你无论如何都要使用的类。在程序包管理器控制台中运行以下命令:
Install-Package Dapper.Contrib
有了这个,我们终于准备好继续前进了。
小总结:
我们现在有一个数据库,包括一个包含我们的TODO列表项的表,并且我们已经安装了数据库框架(Dapper)。在下一篇文章中,我们将继续努力创建 ASP.NET MVC 数据库驱动的 TodoListItem。
TODO List: Models, ViewModels & Helpers
正如前文所述,我们将在本章中构建一个数据库驱动的TODO List清单。在前面的文章中,我们已经做了必要的准备,包括设置数据库和在我们的项目中添加对Dapper数据库框架的支持。现在是时候进行有趣的部分了:我们将开始进行实际的编码!
对前几篇文章的快速总结:到目前为止,您应该有一个 ASP.NET MVC核心项目设置,最好称为“TodoList”。在此项目中,你应该已添加“已添加”和“Dapper.Contrib NuGet”包,并且应已设置与 LocalDB 数据库的连接,在其中应添加一个数据库(“待办事项列表”),并在其中添加一个内部包含一个表(“待办事项列表”)。
现在,我们将开始向您的项目添加文件,并将在以后的文章中添加更多文件。作为参考,以下是创建完成后您的项目结构:

当我们完全完成时,我们将有一个单页的TODO列表Web应用程序。它将具有添加,编辑和删除项目的功能。项目将显示标题和添加日期,以及显示项目是否已完成的复选框,只需单击一下即可切换。它看起来像这样:

跑题了,让我们回到正文吧。
Helpers :帮助类
为了获得与数据库的连接,我们将创建 SqlConnection类的新实例。它实现了 IDbConnection 接口,并且由于 Dapper,该实例将自动扩展为 Dapper 框架中的所有强大功能。
实例化 Sql 连接类时,我们需要提供所谓的连接字符串。此字符串包含连接到相关数据库所需的信息:最重要的是,数据库服务器的 IP/主机名,但通常还包括用户名和密码,以及要使用的数据库的名称。但是,由于我们当前连接到 LocalDB 版本而不是完整的 SQL Server,因此我们可以省略用户名/密码部分,只提供实例的名称以及要使用的数据库。
连接字符串通常存储在配置文件中,但我们现在不会深入讨论。相反,我们将实现一个名为 DbHelper 的帮助器类,它可以为我们提供 SqlConnection 类的正确初始化实例,因此我们不必在每次使用数据库时都写入整个连接字符串。这个非常简单的类现在只有一个静态方法,它看起来像这样:
using System; using System.Data.SqlClient; namespace TodoList.Helpers { public class DbHelper { public static SqlConnection GetConnection() { return new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TodoList;"); } } }
确保使用的连接字符串与计算机上所需的连接字符串匹配。可以通过在 SQL Server 对象资源管理器中右键单击数据库并选择“属性”来验证这一点。在出现的“属性工具”窗口中,检查“连接字符串 - 如果不匹配”,只需将上述代码中找到的版本替换为您自己的版本即可。
Models and ViewModels :模型和视图模型
在我们的项目中,我们需要一个模型:TodoListItem 模型。它是我们存储在数据库中的数据的 .NET 表示形式,因此您将识别出我们创建数据库表时的属性名称 (TodoListItems)。以下是它的代码:
using System; using System.ComponentModel.DataAnnotations; namespace TodoList.Models { public class TodoListItem { public int Id { get; set; } public DateTime AddDate { get; set; } [Required] [MinLength(2, ErrorMessage = "Title must contain at least two characters!")] [MaxLength(200, ErrorMessage = "Title must contain a maximum of 200 characters!")] public string Title { get; set; } public bool IsDone { get; set; } } }
请注意,我已将一些模型验证数据注释添加到 Title 属性中 - 由于这是该类中唯一可由用户编辑的部分,因此我们只需要对此属性进行验证。在这种情况下,我们只需要对长度进行一些基本的验证:最小长度为2,因为很难用更少的字符表示需要完成的事情,并且最大长度为200以匹配数据库表中的列,该列也已设置为最多包含200个字符。
我们还需要一个视图模型(ViewModel)。它将用于控制器(Controller)和视图(View)之间的通信:
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using TodoList.Models; using Dapper; using TodoList.Helpers; namespace TodoList.ViewModels { public class TodoListViewModel { public TodoListViewModel() { using(var db = DbHelper.GetConnection()) { this.EditableItem = new TodoListItem(); this.TodoItems = db.Query<TodoListItem>("SELECT * FROM TodoListItems ORDER BY AddDate DESC").ToList(); } } public List<TodoListItem> TodoItems { get; set; } public TodoListItem EditableItem { get; set; } } }
这个ViewModel基本上只是一个容器,它将存储两个东西:TODO项目的完整列表(在TodoItems属性中)以及一个名为EditableItem的属性。后者将用于表示当前正在编辑的项目,或者如果没有正在编辑的项目,则表示对将添加到列表中的下一个可能项目的引用。这将允许我们使用完全相同的表单,无论是创建新项目还是编辑现有项目。
您还会注意到,这是我们第一次实际与数据库进行通信!它发生在视图模型的构造函数中,因为每次我们需要此视图模型时,我们还需要 TODO 列表项。请注意,我是如何在数据库(Sql 连接)对象上使用 Query() 方法的:这实际上是一个 Dapper 方法,允许我们提供 SQL 查询(the "SELECT * FROM..." part),这将获取 TodoListItems 表的所有行,然后按其 AddDate 对它们进行排序。然后,Dapper将执行它的魔法,并自动将这些数据库表行转换为 TodoListItem 类的 .NET 对象!
小总结
有了所需的模型和视图模型,以及我们的 DbHelper 类,我们就可以继续下一步了 - 在下一部分中,我们将添加完成此项目所需的视图和控制器。
TODO List: The View 视图部分

由于我们的 Web 应用程序只有一个视图,因此不需要可重用的布局。相反,我们只需要在索引.cshtml 视图中包含所有必需的 HTML。标记非常简单,但有两个特别重要的部分需要注意:FORM,用于添加/编辑项目,然后是为 TODO 列表项生成行的循环。
由于文章整体篇幅,需要具体解释代码的小伙伴可以去了解一下View中@和表单提交的知识,也可以查看原文: TODO List: The View - The ASP.NET Core MVC Tutorial (mvc-tutorial.com)
完整View代码:
@model TodoList.ViewModels.TodoListViewModel <!DOCTYPE html> <html> <head> <title>TODO List</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> </head> <body style="margin: 20px;"> <h1>TODO</h1> <table class="table table-striped table-bordered table-hover" style="max-width: 500px;"> <tr> <td colspan="3"> @using(var form = Html.BeginForm("CreateUpdate", "Home", FormMethod.Post)) { @Html.HiddenFor(model => model.EditableItem.Id) <div class="input-group"> @Html.TextBoxFor(model => model.EditableItem.Title, new { @class = "form-control" }) <div class="input-group-append"> <button type="submit" class="btn btn-success">@(Model.EditableItem.Id > 0 ? "Update" : "Add")</button> </div> </div> @Html.ValidationSummary() } </td> </tr> @foreach(var item in Model.TodoItems) { <tr> <td> <input type="checkbox" checked="@item.IsDone" onclick="window.location.href = '/Home/ToggleIsDone/@item.Id';" /> <a href="/Home/Edit/@item.Id"> @item.Title </a> </td> <td class="text-right"> @item.AddDate.ToShortDateString() </td> <td class="text-center"> <a href="/Home/Delete/@item.Id" onclick="return confirm('Are you sure?');" class="btn btn-sm btn-danger">Delete</a> </td> </tr> } </table> </body> </html>
有了视图,我们现在只需要处理我们的构造函数,它将视图模型/模型与视图粘合在一起。请转到下一部分,阅读有关它的所有信息。
TODO List: The Controller
在前几篇文章中,我们为TODO List web应用程序设置了一个数据库。我们添加了一个Model、一个ViewModel和一个Helper类,甚至还添加了View来直观地表示列表。现在是时候添加粘合剂了:控制器( Controller)。
控制器
我们当前创建的 TODO 列表 Web 应用程序将仅用于一个目的:它将是一个 TODO 列表!因此,我们只需要一个控制器,称为 HomeController。在较大的应用程序中,它可能被称为 TodoController或 TodoList Controller,但是通过将其称为HomeController,我们可以从 Web 应用程序的根目录访问Index视图(这也是唯一需要的视图)。
我们的控制器将具有几个操作,这些操作将允许我们将新项目添加到TODO列表,以及编辑或删除现有项目。所有这些操作都需要它们自己的方法,这可能需要一些解释,所以我将首先单独介绍所有方法,然后您将在最后获得完整的Controller代码。
Index():
public IActionResult Index() { TodoListViewModel viewModel = new TodoListViewModel(); return View("Index", viewModel); }
这里没发生什么事。因为我们的TODO List应用程序只有一个View,所以这就是我们交付它的地方。因为视图使用TodoListViewModel,所以我们在这里创建它的一个实例(它将加载TODO项,如前一篇文章中所述),然后使用View()方法将它传递给视图。
Edit():
public IActionResult Edit(int id) { TodoListViewModel viewModel = new TodoListViewModel(); viewModel.EditableItem = viewModel.TodoItems.FirstOrDefault(x => x.Id == id); return View("Index", viewModel); }
Edit()操作的工作原理与Index()操作类似,除了中间行之外,我们将EditableItem属性设置为用户通过id参数请求的项。我们重用相同的视图(Index),它将自动响应我们现在传入的一个现有的TodoListItem。
Delete():
public IActionResult Delete(int id) { using(var db = DbHelper.GetConnection()) { TodoListItem item = db.Get<TodoListItem>(id); if(item != null) db.Delete(item); return RedirectToAction("Index"); } }
Delete()方法很像Edit()方法,它接受一个名为id的参数——有了这个参数,我们就可以从数据库中找到请求的项目(使用来自Dapper.Contrib的Get()方法),当我们有了它,就可以对它调用Delete()方法(同样来自Dapper.Contrib)。这是一个很好的例子,说明Dapper和Dapper。Contrib让一切都变得更简单——我们不用编写SQL来获取并删除一行,只需使用简单的.net方法。
CreateUpdate():
public IActionResult CreateUpdate(TodoListViewModel viewModel) { if(ModelState.IsValid) { using(var db = DbHelper.GetConnection()) { if(viewModel.EditableItem.Id <= 0) { viewModel.EditableItem.AddDate = DateTime.Now; db.Insert<TodoListItem>(viewModel.EditableItem); } else { TodoListItem dbItem = db.Get<TodoListItem>(viewModel.EditableItem.Id); var result = TryUpdateModelAsync<TodoListItem>(dbItem, "EditableItem"); db.Update<TodoListItem>(dbItem); } } return RedirectToAction("Index"); } else return View("Index", new TodoListViewModel()); }
Controller中最复杂的方法是CreateUpdate()方法。由于我们在视图中的FORM将用于添加新项和编辑现有项,处理请求的方法也需要兼顾这两种情况。
我们要做的第一件事是检查 ModelState.IsValid属性——根据我们在前一篇文章中添加的模型验证,这将告诉我们我们的模型是否可以被验证。如果它无效,我们调用View()方法并简单地再次显示Index视图——然而,ModelState将被包括在内,并将在视图中显示的验证摘要中使用它,以显示在验证模型时生成的验证错误。
如果Model是有效的,我们查看EditableItem的Id属性——如果它不大于0,那么我们处理的是一个新项。我们只需将当前DateTime分配给AddDate属性,然后调用Insert()方法将新项插入到数据库中。
如果Id大于0,则表示处理的是一个已存在的项。我们从数据库中获取真正的项,然后在它上调用TryUpdateModelAsync()方法。这将用在发布到服务器的Model中找到的值更新我们从数据库获得的对象,在本例中基本上就是Title属性。一旦我们的模型被更新,我们将通过调用Update()方法将更改写回数据库。
当所有这些都完成后,我们只需执行一个重定向到Index操作,本质上是将一切恢复到以前的方式。当调用Index操作并加载ViewModel时,所做的更改将自动反映出来。请注意,在此过程中我们不进行任何错误检查或异常处理,以使示例尽可能紧凑——当然,您至少应该处理真实应用程序中任何可能的异常。
ToggleIsDone:
public IActionResult ToggleIsDone(int id) { using(var db = DbHelper.GetConnection()) { TodoListItem item = db.Get<TodoListItem>(id); if(item != null) { item.IsDone = !item.IsDone; db.Update<TodoListItem>(item); } return RedirectToAction("Index"); } }
我们用视图中的一小段 JavaScript 激活此操作。它将简单地获取由 Id 参数指定的 TodoListItem,使用数据库上的 Get() 方法。获得该项目后,如果 IsDone 当前为 True,则将其更改为 False,反之亦然。完成此操作后,我们将使用 Update() 方法更新数据库中的相应行。
完整的代码:
using System; using System.Linq; using Microsoft.AspNetCore.Mvc; using TodoList.Models; using Dapper; using Dapper.Contrib.Extensions; using TodoList.ViewModels; using TodoList.Helpers; namespace TodoList.Controllers { public class HomeController : Controller { public IActionResult Index() { TodoListViewModel viewModel = new TodoListViewModel(); return View("Index", viewModel); } public IActionResult Edit(int id) { TodoListViewModel viewModel = new TodoListViewModel(); viewModel.EditableItem = viewModel.TodoItems.FirstOrDefault(x => x.Id == id); return View("Index", viewModel); } public IActionResult Delete(int id) { using(var db = DbHelper.GetConnection()) { TodoListItem item = db.Get<TodoListItem>(id); if(item != null) db.Delete(item); return RedirectToAction("Index"); } } public IActionResult CreateUpdate(TodoListViewModel viewModel) { if(ModelState.IsValid) { using(var db = DbHelper.GetConnection()) { if(viewModel.EditableItem.Id <= 0) { viewModel.EditableItem.AddDate = DateTime.Now; db.Insert<TodoListItem>(viewModel.EditableItem); } else { TodoListItem dbItem = db.Get<TodoListItem>(viewModel.EditableItem.Id); TryUpdateModelAsync<TodoListItem>(dbItem, "EditableItem"); db.Update<TodoListItem>(dbItem); } } return RedirectToAction("Index"); } else return View("Index", new TodoListViewModel()); } public IActionResult ToggleIsDone(int id) { using(var db = DbHelper.GetConnection()) { TodoListItem item = db.Get<TodoListItem>(id); if(item != null) { item.IsDone = !item.IsDone; db.Update<TodoListItem>(item); } return RedirectToAction("Index"); } } } }
恭喜您,您已经构建了自己的数据库驱动的TODO List web应用程序!现在向其中添加一些项目,单击标题编辑项目,单击复选框切换IsDone属性,或者单击Delete按钮删除项目。所有的更改都将反映在数据库中,并在下次加载页面时可见-太棒了!
Happy ending!有任何问题以及建议可以在评论区指出,也可发送邮件到邮箱1176087034@qq.com

浙公网安备 33010602011771号