ASP.NET后端笔记

架构

创建项目后,会默认实现一个天气预报的API,通过Program.cs文件来配置后端内容,Controllers负责定义具体的API内容。

ASP.NET WebCore API项目的架构很简单,就是由很多个Controllers构成,其余的部分都会由框架自动生成,比如说URL解析,接收并监听端口等服务端必需的功能。但是如果想要实现身份验证、连接数据库,就需要额外的配置,推荐使用Docker来部署后端来简化内部连接。

无论采用哪一个数据库,后端在设计的时候都需要一个和具体的数据库无关的数据结构,也就是ORM(数据库关系映射)。同时对数据库的表的设计不应该直接使用SQL语句进行编辑,而是根据定义的数据结构来自动创建和修改,也就是迁移。

注意连接和迁移是两码事,连接指的是后端运行时,而迁移无关后端,可以在任何项目中发生。

数据库连接

这里我们采用Microsoft.AspNetCore.Identity.EntityFrameworkCore包来执行,并且在Docker-Compose的文件中设置连接数据库的key,比如:

api:
    environment:
      - ConnectionStrings__DefaultConnection=Server=db;Port=${DB_PORT};Database=${POSTGRES_DB};User Id=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}

在确认了连接之后,还需要在Program.cs中配置将数据库和DbContext关联起来,这里就和特定的数据库类型有关了,因此需要下载对应的包,比如Microsoft.EntityFrameworkCore.Sqlite包,然后加入如下的代码:

builder.Services.AddDbContext<AppContext>(options =>
    options.UseSqlite(configuration.GetConnectionString("DefaultConnection")));

如此在启动时便自动连接上了数据库,注意环境变量的设置不仅仅可以通过Docker-Compose来设置,只是如果使用Docker启动这样做最方便,同时为了保证隐私,需要使用.env文件将重要的字段分开存储,并从版本管理中排除。

数据库迁移

首先迁移进行的前提就是有一个DbContext,比如:

public class AppContext : DbContext
{
    public AppContext(DbContextOptions<AppContext> options) : base(options){ }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
     	// ...数据关系定义(外键,主键)
    }

    public DbSet<Song> SongData { get; set; }
    public DbSet<Game> GameData { get; set; }
}

通过程序包管理器控制台或者dotnet命令行(需要在项目根目录下执行)可以执行迁移文件的生成和执行。指令如下:

# 生成迁移文件
Add-Migration <MigrationName>
# 或者是
dotnet ef migrations add <MigrationName>

# 移除上一个迁移文件
Remove-Migration
# 或者是
dotnet ef migrations remove

# 执行迁移
Update-Database
# 或者是
dotnet ef database update

迁移文件的结构由Up和Down函数组成,分别用于正向将数据库迁移,反向将数据库还原,在生成之后可以手动修改细节来微调结构。

Controller

处理完数据库之后,后端的任务就是根据请求URL来执行业务逻辑并返回,这里通过Controller来自动实现URL的解析,只需要将关注放在具体的业务函数上即可。一个典型Controller的结构如下:

[ApiController]
[Route("api/[controller]")]
public class GameController : ControllerBase
{
    // GET: api/game
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Game>>> GetGames()
    {
        //...
        return Ok(games);
    }

    [HttpGet("{gameId}/versions")]
    public async Task<ActionResult<IEnumerable<string>>> GetGameVersions([FromRoute] int gameId)
    {
        //...
        if (!gameExists) return NotFound($"Game with ID {gameId} not found.");
		//...
        return Ok(game);
    }
}

[ApiController]属性指出该类是一个构建API所需的控制器,[Route]属性指出解析URL时,路径的前缀是什么,[HttpGet]属性指出该函数对应的API URL后缀是什么,[FromRoute]表示从URL中获取的一个变量,变量名需要和[HttpGet]中完全一致。

在此例子中,GetGames的URL是{IP:Port}/api/game,而GetGameVersions的是 {IP:Port}/api/game/{gameId}/versions,其中 gameId 表示任何一个int,将作为变量输入函数。