使用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 激活此操作。它将简单地获取由 I参数指定的 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

 

posted @ 2022-09-23 10:03  林子卿  阅读(256)  评论(0)    收藏  举报