作者:陈广 日期:2018-8-6
之前讲解了 Razor 的增删改简单示例,现在还差 MVC 没讲。那么这篇文章就讲 MVC 示例吧,效果还是按照之前的Razor示例来,只是把实现方法由 MVC 改成 Razor。
传统的 ASP.NET 应用程序使用的是 MVC 方式,也就是将应用程序分为 Model、View 和 Controller 层。而在 Razor 应用程序中,Controller 层和 Model 层是放在一起的,这样就简化了应用程序的复杂度,对于小型应用来说更为合适。
MVC 层中的 View 层是展示层,也就是在浏览器中显示的内容,我们可以把它想象成客户端的内容(实际上是浏览器向服务器发送请求,服务器返回页面 HTML,浏览器解析 HTML 并显示相应的内容)。客户端页面上有不同的按钮,点不同的按钮会向服务器申请不同的操作,而 Controller 层则是一个中转站,对不同的请求做相应处理,并导向不同的页面。可以把它想象成一个快递投递点,邮件从各地源源不断地进入投递点,然后再由投递点根据投递地址分配到不同地方。Controller 在处理请求的过程中可能会使用到数据库里的数据,它不会直接跟数据库打交道,而是通过一个代理人,Model就是这个代理人。这好比你开超市要卖全国各地的货,你不可能到全国各地直接找每个厂家要货,这太麻烦,成本太高。这时你就会找厂家在当地的经销商进货,和厂家打交道的麻烦事由经销商完成,这使得你可以专注于超市经营。
其实 Model 层并不直接操作数据库,Model 层里存在的是 C# 类,而这些类又跟数据库里的表一一对应。也就是说 Model 层将数据库架构映射为 C# 类。而 Model 层和数据库之间是通过 EF Core 进行连接的,实际上操作数据库的是 EF Core。另外,以上对 Model 的描述并非完全准确,如果数据存在复杂业务逻辑,也应该是放在 Model 层,Controller 只负责转发。这一点我们在将来写复杂的应用程序时再演示。
本项目使用 .NET Core 2.1 以上版本,这样在使用数据库时无需引入额外的 nuget 包。
dotnet new empty
创建完项目后,可以看到自动生成的【MvcDemo.csproj】文件,打开它,确保<TargetFramework>
标记内的 .NET Core 版本是 2.1 以上。
上次我们写 Razor 时,是使用 json 来代替数据库来存放数据。之前由于我们初步学习了 EF Core,现在就可以使用数据库了,如果使用的是 Visual Studio,在安装vs时,默认会安装 SQL Server,可以直接使用 SQL LocalDB,非常方便。但如果没有安装vs,就会有些麻烦。我现在是在外地使用笔记本写的这篇文章,不想装vs,也不想装 SQL Server 或 PostgreSQL,虽然可以远程访问我服务器上装的 PostgreSQL,但文章就不具有实用价值了,毕竟不是每个人都买有服务器。如何解决?这让我陷入了深深地深思。当然,这不难,有没有一个数据库体积小、安装方便、应用广泛,最重要的是 EF Core 还支持它?很幸运,SQLite 就是这么一款数据库。SQLite 号称嵌入式数据库,也就是在嵌入式系统里使用的数据库,体积自然会非常小,所有文件加起来不到 5M。拷贝即用,无需安装,在 Android 操作系统里有广泛应用。我们在vs code里使用都不需要安装,直接引入一个 nuget 包即可。
在vs code中按下【Shift+Ctrl+Y】键打开终端,输入如下命令:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
安装完成后会弹出一个对话框,点击 Restore 按钮即可引用此包。如下图所示: 如果没有出现 Restore 对话框也可以输入如下命令达到同样效果:
dotnet restore
在《Razor增删改简单示例》这篇文章中,我们可以了解到数据库非常简单,只有一张 Studentd 表,下面我们针对这张表创建实体类。在 MvcDemo 下创建一个名为 Models 的文件夹。在 Models 文件夹下新建一个 Student.cs 文件,输入如下代码:
namespace MvcDemo.Models
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
}
在 Models 文件夹下新建一个 DataContext.cs 文件,输入如下代码:
using Microsoft.EntityFrameworkCore;
namespace MvcDemo.Models
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Student> Students { get; set; }
}
}
在 MvcDemo 下新建一个 appsettings.json 文件,我们将在这里存放数据库连接字符串:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=Stuinfo.db"
}
}
如果你需要使用 SQL LocalDB,内需更改 DefaultConnection 项里的连接字符串即可,在.NET Core 2.1 下无需引入任何 nuget 包。
打开 Startup.cs 文件,增加如下命名空间:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MvcDemo.Models;
更改Startup
类代码如下:
public Startup(IConfiguration config) => Configuration = config;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
string conStr = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<DataContext>(options => options.UseSqlite(conStr));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
下面我们使用命令的方式生成相应的数据库。在终端输入如下两条命令:
dotnet ef migrations add Initial
dotnet ef database update
两条命令执行完毕后,我们在资源管理器串可以看到相应的 SQLite 数据库文件已经生成,如下图:
现在问题来了,Stuinfo.db 是一个二进制文件,vs code是无法打开它的,打开了也是乱码。我又如何知道学生表是否在此数据库中已经被创建了呢?不用担心,万能的 vs code 可以轻易解决这个问题。
点击vs code 左侧的 扩展 按钮(也可使用【Ctrl+Shift+X】热键调出,但有可能快捷键已被其它插件占用)打开扩展窗口,在搜索栏输入 SQLite,可搜索到 SQLite插件,如下图所示: 点击插件右下方的安装按钮以安装插件,安装完毕后点击重新加载按钮: 现在,我们可以查看 SQLite 数据库了。
按下【Ctrl+Shift+P】打开命令面板(也可通过菜单【查看】-->【命令面板】打开)。输入SQLite
命令并选择其中的 Open Database in Explorer 命令:
接下来观察vs code的资源管理最下方,出现了【SQLITE EXPLORER】,展开它,可观察到【Stuinfo.db】数据库,继续展开,出现【Students】表及它的字段。如下图所示,我们已经成功在 SQLite 中创建了相应的表。
接下来往 Students 表中添加两条数据数据以方便我们查看页面效果。
在命令面板中选择【SQLite:Quick Query】命令 : 依次输入两条 SQL 查询语句:
insert into Students values(1,"ZhangSan")
insert into Students values(2,"LiSi")
最后,输入以下语句查看插入效果:
select * from Students
在弹出的 SQLite 窗口中可以查看到如下图所示的表中数据,表明成功插入两条数据。 请注意,不要插入中文,否则显示乱码。
如果使用 MVC 模板创建项目,会自动包含公共页面,现在我们是白手起家的操作方式,所以这些东西还得自力更生。
在 MvcDemo 项目下新建一个 Views 文件夹,然后在 Views 文件夹内新建一个 Home 文件夹。接下来在 Home 文件夹下新建一个 _Layout.cshtml 文件,输入如下代码:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>学生信息</title>
</head>
<body>
@RenderBody()
</body>
</html>
此页面的内容会在所有页面显示。
在 Views 文件夹下新建一个名为 _ViewStart.cshtml 的文件,输入如下代码:
@{
Layout = "_Layout";
}
此文件指定了公共页,"_Layout"
正是刚刚创建的公共页的文件名。
在 Views 文件夹下新建一个名为 _ViewImports.cshtml 的文件,输入如下代码:
@using MvcDemo.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
此处引入的内容会作用于 Views 文件夹下的所有页面,所以所有页面都需要引用的东西都应该放在这里。
下面我们来创建主页面,主页面主是用来显示所有学生的信息。
Razor中,控制器是和页面放在一起的,但 MVC 中,页面和控制器是分开的,所以需要单独创建控制器。
在 MvcDemo 项目下新建一个 Controllers 文件夹,然后在 Controllers 文件夹下新建一个 HomeController.cs 文件,输入如下代码:
using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;
namespace MvcDemo.Controllers
{
public class HomeController : Controller
{
private DataContext context;
public HomeController(DataContext ctx) => context = ctx;
public IActionResult Index() => View(context.Students);
}
}
这里创建的控制器的文件名是有讲究的,Home前缀正好对应 Views 文件夹下的 Home 文件夹名称,表明此控制器操作的是 Home 文件夹下的页面。在访问页面时,如果不在 URL 中指定任何后缀,则默认调用控制器中的Index()
方法。Index()
方法将所有学生信息context.Students
作为View
的参数返回给页面进行处理。
下面创建主页面。在 Home 文件夹下创建一个 Index.cshtml 文件,输入如下代码:
@model IEnumerable<Student>
<h1>学生列表</h1>
<table class="table">
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach(var stu in Model)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
</tr>
}
</tbody>
</table>
这里需要注意 MVC 和 Razor 的不同之处,以及第一句@model IEnumerable<Student>
和之前控制器Index()
方法中的View(context.Students)
之间的关系。
Index()
方法处理。Index()
方法返回 View(context.Students)
,表示返回 Index.cshtml 页面并将context.Students
作为参数传递过去。context.Students
的就是@model IEnumerable<Student>
,它表示参数必须是一个实现了IEnumerable
接口的Student
类集合。在 Index.cshtml 中可通过Model
来使用此参数。现在可以运行程序查看效果了。在终端输入如下命令来运行程序:
dotnet run
效果如下图所示:
按住 Ctrl 键的同时用鼠标点击如上图所示的http://localhost:5000
即可打开浏览器并导航到主页面。当然你也可以打开浏览器,然后在地址栏输入http://localhost:5000
来访问主页面。
程序运行效果如下图所示: 现在已经可以显示之前我们输入的两条数据了。
下面参照之前的《Razor增删改简单示例》这篇文章,创建 Create 页面,用于向数据库中添加学生数据。
打开 HomeController.cs 文件,并更改代码如下:
using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;
namespace MvcDemo.Controllers
{
public class HomeController : Controller
{
private DataContext context;
public HomeController(DataContext ctx) => context = ctx;
public IActionResult Index() => View(context.Students);
//以下为新增代码
public IActionResult Create() => View();
[HttpPost]
public IActionResult Create(Student stu)
{
context.Students.Add(stu);
context.SaveChanges();
return RedirectToAction(nameof(Index)); //重定向到主页面
}
}
}
我们在控制器中新增了两个Create
方法:
Create
方法直接返回 Create.cshtml页面,此页面稍后会创建。当我们在浏览器中输入http://localhost:5000/home/create 地址时,controller会调用此方法以显示新建学生页面。Create
方法被标识为[HttpPost]
特性,表明只有在method="POST"
的表单中点击了提交按钮,才会被导航到此方法。下面来制作新建学生页面。在 View/Home 文件夹下新建一个文件,命名为 Create.cshtml。输入如下代码:
@model Student
<html>
<body>
<p>请输入新的学生信息</p>
<form asp-action="Create" method="POST">
<div>学号:<input asp-for="Id"/></div>
<div>姓名:<input asp-for="Name"/></div>
<input type="submit" value="提交"/>
</form>
</body>
</html>
表单form
中的标签助手asp-action="Create"
表示此表单提交后会交由 Controller 中的Create
方法进行处理。method="POST"
对应的正是 Controller 中的[HttpPost]
特性。
接下来需要在主页面添加一个导航到 Create.cshtml 页面的链接,打开 Index.cshtml 文件,并在最后添加一句代码:
<a asp-action="Create">新建</a>
接下来运行程序,并添加几条数据。如果过程有不清楚的可以参考《Razor增删改简单示例》这篇文章的配图。我就不再做一遍了。
删除功能比较简单,不需要添加新的文件。
打开 HomeController.cs 文件,在HomeController
类中添加一个新的方法:
[HttpPost]
public IActionResult Delete(Student stu){
context.Students.Remove(stu);
context.SaveChanges();
return RedirectToAction(nameof(Index));
}
使用[HttpPost]
表明在页面文件中需要使用表单来完成删除功能。
打开 View/Home/Index.cshtml 文件,修改代码如下:
@model IEnumerable<Student>
<h1>学生列表</h1>
<table class="table">
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach(var stu in Model)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
<!-- 以下为新添加的代码 -->
<form asp-action="Delete" method="POST">
<input type="hidden" name="Id" value="@stu.Id" />
<td><button type="submit">删除</button></td>
</form>
</tr>
}
</tbody>
</table>
<a asp-action="Create">新建</a>
需要注意,为了使用《Razor增删改简单示例》这篇文章中的效果而使用按钮进行删除,这里使用了表单,所以必须要增加了一个<input>
标签来存放学生的Id
字段。在 MVC 中暂时没找到象 Razor 那样使用asp-page-handler
标签助手就能方便解决问题的方法。你也可以使用<a>
标签来很方便实现删除功能,参考之前的【新建】链接。
打开 HomeController.cs 文件,在HomeController
类中添加2个新的方法:
public IActionResult Edit(int key)
{
Student stu = context.Students.Find(key);
return View(stu);
}
[HttpPost]
public IActionResult Update(Student stu)
{
context.Students.Update(stu);
context.SaveChanges();
return RedirectToAction(nameof(Index));
}
和 Create 一样,需要写两个方法。
Edit(int key)
用于将链接导向名为Edit.cshtml
的页面,链接中需要给出学生的 Id 以查找出相应的学生并传递给 Edit 页面进行修改。return View(stu);
用于返回 Edit 页面并将学生对象传递过去。Update(Student stu)
则用于在 Edit 页面修改完成后的保存工作,最终它会导向主页面显示修改后的效果。在 Views/Home 文件夹下新建一个名为 Edit.cshtml 的文件,输入代码如下:
@model Student
<h1>编辑学生-Id</h1>
<form asp-action="Update" method="POST">
<input asp-for="Id" type="hidden"/>
<div>
<input asp-for="Name"/>
</div>
<div>
<button type="submit">保存</button>
</div>
</form>
注意,文件名 Edit 对应控制器中的Edit
方法。asp-action="Update"
表明单击底下的按钮会导航到控制器中的Update
方法。
打开 View/Home/Index.cshtml 文件,修改其中的<tbody>
代码如下:
<tbody>
@foreach(var stu in Model)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
<!-- 下面这句为新添加的代码 -->
<td><a asp-action="Edit" asp-route-key="@stu.Id">编辑</a></td>
<form asp-action="Delete" method="POST">
<input type="hidden" name="Id" value="@stu.Id" />
<td><button type="submit">删除</button></td>
</form>
</tr>
}
</tbody>
asp-action="Edit"
指定调用控制器中的Edit
方法,asp-route-key
则传递了学生Id。
现在可以运行程序,并编辑数据了。效果和《Razor增删改简单示例》这篇文章中的一样,我就不再配图了。
之所以把这个例子跟之前的 Razor 做得一模一样,主要是让大家感受到 Razor 和 MVC 的某些细节上的差异。我个人感觉最大的不同是 MVC 对数据库中的同一个表,不管有多少页面,处理方法都可以放在同一个控制器内,而 Razor 则是每个页面都有自己的控制器。Razor 页面可以很方便找到自己的控制器代码,MVC 则要费一些周折。孰优熟劣大家自己感受,又或说它们都有自己适合的应用场景。
;