Dockerizing a ASP.NET Core Web API App and SQL Server
微软开发了一个开源的、跨平台的框架,取代了旧的经典ASP.NET,称为ASP.NET Core。
该框架以构建现代的、基于云的、与互联网连接的网络应用程序和服务而闻名。
企业可以决定是在内部还是在云环境中部署应用程序。
Docker是允许开发者部署和运行容器的技术。它为应用程序提供了一个完整的运行环境,如操作系统和系统库,使其能够顺利地独立运行。
通过容器和对云友好的ASP.NET Core的结合,可以很容易地在云中运行高性能的.NET 服务。
本教程将讨论如何配置ASP.NET Core应用程序和SQL Server 2019在Docker容器上运行。
为了实现这一功能,我们将使用ASP.NET Core和SQL Server 2019构建一个电影列表应用程序。
前提条件
要继续学习本教程,你需要具备以下条件。
- 安装了桌面版[Docker]
- 安装了最新的[SQL Server Management Studio]
- 安装了[Visual Studio 2019]
- 熟练掌握[Docker命令]、[SQL Server命令]和[ASP.NET Core]语言。
设置SQL Server Docker镜像
我们将首先通过在终端执行以下命令,拉出最新的SQL Server 2019容器镜像。
$ docker pull mcr.microsoft.com/mssql/server:2019-latest
接下来,我们将使用以下命令继续运行我们的Docker容器。
$ docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=2Secure*Password2" -p 1450:1433 --name sqlserverdb -h mysqlserver -d mcr.microsoft.com/mssql/server:2019-latest
上面的命令包含以下内容。
--name:这是我们的容器的名称,在我们的例子中是sqlserverdb。- 我们为我们的SQL服务器设置的密码是
2Secure*Password2。该密码必须足够强大,并满足最低密码要求。 -p 1450:1433:这些是我们要暴露的端口。
现在Docker容器已经启动并运行了。
使用SSMS连接SQL服务器
在这里,我们将使用SQL Server Management Studio工具来连接我们运行在Docker容器中的SQL服务器。
为了实现这一目标,我们将在SSMS连接窗口中输入以下细节。
- 我们将给我们的服务器命名为
localhost, 1450。人们可以根据配置选择使用一个不同的IP地址。在我们的案例中,我们使用了localhost和1433作为我们配置的端口。 - 对于SQL服务器的配置登录,我们使用了
SA,密码为2Secure*Password2。
之后,我们点击Connect 按钮。它将确保我们与已经在我们的Docker容器中运行的SQL服务器连接。

一旦连接建立,我们就可以与数据库进行交互。
在ASP.NET Core应用程序中创建CRUD操作
我们将从启动Visual Studio开始。然后,我们选择ASP.NET Core Web App (Model-View-Controller) 模板,如下图所示。

接下来,我们将我们的ASP.NET Core应用程序命名为DockerSqlAsp 。确保不勾选Place solution and project in the same directory 这个名字下的复选框,如下图所示。

然后,点击next ,我们继续配置我们的新项目。在这个阶段,我们将不勾选Enable Docker Support 的复选框。我们将点击create 按钮,如下图所示。

使用Entity Framework Core创建数据库
在这一节中,我们将在Docker容器中运行的SQL Server中创建一个新的数据库并将其命名为FilmDB 。
我们将只有一个名为Film 的表,我们将从ASP.NET Core应用程序中执行CRUD操作。
EF Core将处理这些操作。
前端ASP.NET Core应用程序将与运行在Docker上的后端SQL Server数据库交互,如下图所示。

在我们开始编码我们的应用程序之前,我们必须确保下面的NuGet 包已经被安装。
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
接下来,我们将在Models 目录内创建一个名为Film.cs 的新类,并粘贴以下代码。
using System.ComponentModel.DataAnnotations;
namespace DockerSqlAsp.Models {
public class Film
{
[Required]
public string ReleaseYear { get; set; }
[Required]
[Key]
public int No { get; set; }
[Required]
[StringLength(50, ErrorMessage = "Movie name cannot exceed 50 characters.")]
public string MovieName { get; set; }
}
}
之后,我们将在同一目录下创建一个类,作为我们EF Core的数据库上下文,并将其命名为FilmContext.cs 。
然后,我们需要在该文件中粘贴以下代码。
using Microsoft.EntityFrameworkCore;
namespace DockerSqlAsp.Models {
public class FilmContext : DbContext {
public FilmContext(DbContextOptions<FilmContext> opt_Db) : base(opt_Db) {
}
public DbSet<Film> Film { get; set; }
}
}
设置一个控制器
由于我们已经完成了我们的模型,我们可以在Controllers 目录中创建一个名为FileController.cs 的文件。
在这里,我们将定义在我们的应用程序中执行CRUD操作所需的动作方法,如下所示。
using System.Threading.Tasks;
using DockerSqlAsp.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace DockerSqlAsp.Controllers {
public class FilmController : Controller {
private FilmContext ctx;
public FilmController(FilmContext fctx) {
ctx = fctx;
}
public IActionResult AddNew() {
return View();
}
[HttpPost]
public async Task<IActionResult> Rem(int id) {
var del = ctx.Film.Where(b => b.No == id).FirstOrDefault();
ctx.Remove(del);
await ctx.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
[HttpPost]
public async Task<IActionResult> Modify(Film flm) {
if (ModelState.IsValid)
{
ctx.Update(flm);
await ctx.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
return View(flm);
}
[HttpPost]
public async Task<IActionResult> AddNew(Film flm) {
if (ModelState.IsValid) {
ctx.Add(flm);
await ctx.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
return View();
}
public IActionResult Modify(int id) {
var del = ctx.Film.Where(b => b.No == id).FirstOrDefault();
return View(del);
}
public IActionResult Index() {
var flm = ctx.Film.ToList();
return View(flm);
}
}
}
创建用户界面视图
由于我们已经完成了控制器,现在是时候为我们的应用程序创建视图了。
在Views/Home目录下,我们将创建三个文件,并命名为AddNew.cshtml,Modify.cshtml, 和Index.cshtml 。这些视图将作为我们应用程序的用户界面。
我们首先对AddNew.cshtml 进行编码,如下图所示。
@model Film
@{
ViewData["Title"] = "Add a New Movie";
}
<h1 class="bg-info text-white">Add a New Movie</h1>
<a class="btn btn-block btn-success" asp-action="Index">Show all Movies</a>
<div class="text-danger" asp-validation-summary="All"></div>
<form enctype="application/x-www-form-urlencoded" method="post">
<div>
<label>Movie Name</label>
<input class="form-control-range" asp-for="MovieName" type="text" />
<label>Release Year</label>
<input class="form-control-range" asp-for="ReleaseYear" type="text" /><br />
<input class="btn btn-secondary" value="Add New" type="submit" />
</div>
</form>
接下来,我们对文件Modify.cshtml 进行编码,如下图所示。
@model Film
@{
ViewData["Title"] = "Modify a Movie";
}
<h1 class="bg-info text-white">Modify a Movie</h1>
<a class="btn btn-block btn-success" asp-action="Index">Show all Movies</a>
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="No">No.</label>
<input class="form-control-range" type="text" asp-for="No" readonly />
</div>
<div class="form-group">
<label asp-for="MovieName">Movie Name</label>
<input class="form-control-range" type="text" asp-for="MovieName" />
</div>
<div class="form-group">
<label asp-for="ReleaseYear">Release Year</label>
<input class="form-control-range" type="text" asp-for="ReleaseYear" />
</div>
<button class="btn btn-secondary" type="submit">Modify a movie</button>
</form>
最后,我们对文件Index.cshtml 进行编码,如下图所示。
@model List<Film>
@{
ViewData["Title"] = "Movies Release Years";
}
<h1 class="bg-info text-white">Movies Release Years</h1>
<a class="btn btn-block btn-success" asp-action="AddNew">Add a New Movie</a>
<div>
<table class="table">
<tr>
<th>S#</th>
<th>Movie Name</th>
<th>Release Year</th>
<th>Action</th>
<th>Action</th>
</tr>
@foreach (Film flm in Model)
{
<tr>
<td>@flm.No</td>
<td>@flm.MovieName</td>
<td>@flm.ReleaseYear</td>
<td>
<a asp-route-id="@flm.No" class="btn btn-block btn-dark" asp-action="Modify">Edit</a>
</td>
<td>
<form asp-route-id="@flm.No" method="post" asp-action="Rem">
<input class="btn-warning btn btn-block" value=”Remove” type="submit" />
</form>
</td>
</tr>
}
</table>
</div>
执行EF Core迁移
我们将首先在项目解决方案资源管理器中找到并打开文件appsettings.json 。我们将创建一个连接字符串,将我们的前端应用程序连接到SQL Server数据库。
该文件将显示为下面的亮点。
{
"ConnectionStrings": {
"DefaultConnection": "Initial Catalog=FilmDB; Data Source=localhost,1450; Persist Security Info=True;User ID=SA;Password= 2Secure*Password2"
}
}
数据源属性代表SQL Server地址和Docker容器上运行的端口。初始目录值代表数据库名称。
可以将数据源字段改为我们正在使用的机器的内部IP地址。
接下来,我们将导航到文件Startup.cs ,将数据库上下文作为服务添加到ConfigureServices 方法中。
public void ConfigureServices(IServiceCollection config_serv)
{
config_serv.AddDbContext<FilmContext>(filmOpt =>
filmOpt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
config_serv.AddControllersWithViews();
}
我们还需要执行EF Core迁移命令。为了实现这一点,我们将在Visual Studio中导航到包管理器控制台窗口,并运行以下命令。
$ add-migration Migration1
$ database-update
上述命令将在容器中已经运行的SQL Server上生成一个带有FilmDB 的数据库。
测试应用程序的功能
我们现在可以继续并测试我们的应用程序,因为我们尝试了CRUD操作。
我们将在Visual Studio中运行我们的ASP.NET Core应用程序。然后我们点击Add a New Movie 按钮,填写所需的细节,并点击Add New 按钮。
一个new movie 记录将被插入,如下所示。


接下来,我们可以点击Edit 按钮,并测试该应用程序。预期的结果如下所示。


之后,我们可以点击Remove 按钮,删除该记录。

正如我们在上面看到的,我们的应用程序正在按预期工作;我们已经执行了CRUD操作。
为项目设置Docker Compose
接下来,我们将使用Docker Compose在Docker容器内运行ASP.NET Core应用程序和SQL Server 2019。
我们将首先在Visual Studio解决方案资源管理器中右键点击ASP.NET项目名称,并选择容器协调支持选项。

在出现的弹出窗口中,我们将选择Docker Compose 选项。

在接下来出现的窗口中,我们将设置目标操作系统为Linux。

一个名称为docker-compose 的新项目将被创建。
之后,我们将找到Docker Compose的配置文件,名称为docker-compose.yml 。这个文件位于新项目的目录中。我们将编辑该文件,添加一个新的服务名称,名为sqldb 。
然后我们将指定我们之前下载的docker镜像,设置密码和端口。
sqldb:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- SA_PASSWORD=2Secure*Password2
- ACCEPT_EULA=Y
ports:
- "1440:1433"
我们应该注意到端口的问题。对于这一部分,我们将为SQL服务器设置一个与之前不同的端口。这是因为我们将使用一个不同的SQL服务器。
接下来,我们将通过运行EF Core的迁移命令再次迁移数据库。
一旦,我们保存了docker-compose 配置文件,Docker将创建两个容器,一个用于ASP.NET Core应用程序,另一个用于运行SQL服务器。

然后,我们再次编辑位于文件appsettings.json 中的连接字符串。它将协助容纳新的SQL Server,通过编辑端口回到1450 。
{
"ConnectionStrings": {
"DefaultConnection": " Initial Catalog=FilmDB; Data Source=sqldb; Persist Security Info=True;User ID=SA;Password=2Secure*Password2"
}
}
请注意,我们提供的DataSource值是sqldb ,而不是localhost, 1440 。这是因为sqldb 是我们在docker-compose 配置文件中的SQL Server的当前服务名称。
这使得容器之间可以使用它们的名字而不是IP地址进行交互。
运行EF Core迁移
我们将再次编辑数据库连接字符串,使用localhost, 1440 ,而不是sqldb 。
主要原因是,EF core需要知道正在进行迁移的数据库。
修改后的连接字符串将如下图所示。
{
"ConnectionStrings": {
"DefaultConnection": " Initial Catalog=FilmDB; Data Source=localhost,1440; Persist Security Info=True;User ID=SA;Password=2Secure*Password2"
}
}
接下来,在包管理器的控制台窗口中,我们将执行下面的命令。
$ add-migration Migration2
$ database-update
一旦我们成功地迁移了数据库,我们将把连接字符串编辑回我们之前设定的值。
{
"ConnectionStrings": {
"DefaultConnection": " Initial Catalog=FilmDB; Data Source=sqldb; Persist Security Info=True;User ID=SA;Password=2Secure*Password2"
}
}
最好记住,在数据库迁移过程中,SQL Server容器需要运行。
如果不是这样,那么就应该在Visual Studio中重建解决方案,以确保数据库迁移的成功。
我们将在Visual Studio中运行该应用程序,并通过执行CRUD操作来重新测试该应用程序。
总结
在本指南中,我们已经学会了如何为ASP.NET Core应用程序和SQL服务器创建一个Docker容器。
我们还使用了Docker Compose来同时运行两个容器并执行CRUD操作。
我们已经能够提取并使用SQL Server容器镜像来运行SQL Server容器。这一点至关重要,特别是对于那些不打算下载和安装SQL Server到开发环境中的开发者。