在 .NET 8 中轻松构建 REST API!

2025-05-24

在 .NET 8 中轻松构建 REST API!

本文的目的是向您介绍一种使用 ASP.NET 8 而不是更常用的 MVC 控制器来构建 Web API 的替代方法,这种方法对开发人员更加友好。

我们将探索基于 .NET 6 引入的 Minimal API 构建的开源库FastEndpoints ,它能够让我们获得所有性能优势,同时避免 Minimal API 的痛点。与垂直切片架构 (Vertical Slice Architecture)结合使用,无论项目规模或复杂程度如何,代码库的导航更加清晰,项目维护也变得轻而易举。因为框架不会干扰您的工作,您可以专注于系统的工程和功能方面。

让我们动手为 Dev.to 的简化版本构建一个 REST API,让作者/发布者可以注册账户并发布文章。新文章将进入审核队列,网站管理员必须审核通过后才能公开发布。

我们的系统中的主要实体如下:

  • 行政
  • 作者
  • 文章

该系统的功能/用户故事可分类如下:

  • 行政
    • 登录网站
    • 获取需要审核的文章列表
    • 批准文章并发布
    • 拒绝文章并说明理由
  • 作者
    • 现场报名
    • 登录网站
    • 获取自己的文章列表
      • 查看状态[待定/批准/拒绝]
    • 创建新文章
    • 编辑现有文章
  • 公共区域
    • 获取最新 50 篇文章列表
    • 通过 id 获取文章
    • 获取文章的最新 50 条评论
    • 对文章发表评论

使用的技术栈如下:

  • 基础框架: ASP.NET 8
  • 端点框架: FastEndpoints
  • 身份验证方案: JWT Bearer
  • 输入验证: FluentValidations
  • 数据存储: MongoDB
  • API可视化: SwaggerUI

我们走吧...

创建一个新的 Web 项目并使用 Visual Studio 或在终端窗口中运行以下命令来安装依赖项:



dotnet new web -n MiniDevTo
dotnet add package FastEndpoints
dotnet add package FastEndpoints.Swagger
dotnet add package MongoDB.Entities


Enter fullscreen mode Exit fullscreen mode

为我们的功能创建文件夹结构,使其如下所示:

功能文件夹结构

树的最后一层将是一个端点,它可以是我们应用程序的 UI/前端可以调用的命令或查询。查询以 为前缀,Get按照惯例表示它是数据检索,而命令以SaveApproveReject等动词为前缀,表示提交某些状态更改。如果您以前遇到过这种情况,这可能听起来很熟悉CQRS,但我们在这里不像 CQRS 那样区分读取和写入。相反,我们根据 来组织我们的功能/端点Vertical Slice Architecture

FastEndpoints 是REPR 模式的一个实现。我保证,这将是我在这篇文章中讨论的最后一个模式!

REPR 设计模式将 Web API 端点定义为包含三个组件:请求、端点和响应。它简化了常用的 MVC 模式,并更加专注于 API 开发。

因此,为了减轻创建端点所需的多个类文件的繁琐重复工作,请安装FastEndpoints 提供的Visual StudioVS Code扩展。或者,您也可以手动创建这些文件。

程序.cs

首先...让我们更新Program.cs文件使其如下所示:



global using FastEndpoints;
global using FluentValidation;

var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();

var app = builder.Build();
app.UseFastEndpoints();
app.Run();


Enter fullscreen mode Exit fullscreen mode

这就是成为 Web API 项目所需要的全部内容。但是...如果您现在尝试运行该程序,您将遇到如下运行时异常:

InvalidOperationException: “FastEndpoints 无法找到任何端点声明!”

让我们通过使用 VS 扩展创建我们的第一个端点来解决这个问题。

作者注册端点

右键单击Author/SignupVisual Studio 中的文件夹 > 添加 > 新建项。然后选择FastEndpoints Feature FileSet位于Installed > Visual C#节点下的新项目模板。然后对于文件名,输入Author.Signup.cs如下所示:

vs 扩展

它会在你选择的文件夹下创建一组新文件,其中包含特定于此端点的命名空间。打开Endpoint.cs文件并查看顶部的命名空间。它就是我们之前输入的文件名。

当我们打开端点类时,继续用下面的代码替换它的内容:



namespace Author.Signup;

public class Endpoint : Endpoint<Request, Response, Mapper>
{
    public override void Configure()
    {
        Post("/author/signup");
        AllowAnonymous();
    }

    public override async Task HandleAsync(Request r, CancellationToken c)
    {
        await SendAsync(new Response()
        {
            //blank for now
        });
    }
}


Enter fullscreen mode Exit fullscreen mode

这里我们得到了什么?我们有一个从通用基类继承的端点类定义Endpoint<TRequest, TResponse, TMapper>。它有 2 个重写的方法Configure()HandleAsync()

在 configure 方法中,我们指定希望端点监听POST路由上的http 动词/方法/author/signup。我们还指定未经身份验证的用户应该被允许使用该AllowAnonymous()方法访问此端点。

HandleAsync()方法用于编写处理传入请求的逻辑。目前,它只是发送一个空白响应,因为我们尚未向请求和响应 DTO 类添加任何字段/属性。

模型.cs

打开Models.cs文件并用以下内容替换请求和响应类:



public class Request
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
}

public class Response
{
    public string Message { get; set; }
}


Enter fullscreen mode Exit fullscreen mode

Swagger 用户界面

现在,让我们设置 Swagger,这样我们就可以使用 Web 浏览器与端点进行交互,而不是使用 Postman 之类的工具。Program.cs再次打开它,让它看起来像这样:



global using FastEndpoints;
using FastEndpoints.Swagger; //add this

var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();
builder.Services.SwaggerDocument() //add this

var app = builder.Build();
app.UseFastEndpoints();
app.UseSwaggerGen(); //add this
app.Run();


Enter fullscreen mode Exit fullscreen mode

然后打开Properties/launchSettings.json文件并将内容替换为:



{
  "profiles": {
    "MiniDevTo": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "http://localhost:8080",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

更新启动设置不是强制性的,但为了8080本文的目的,让我们修复 API 服务器的监听端口。

接下来,在调试模式下构建并运行你的项目(在 Visual Studio 中按 CTRL+F5)。打开你的 Web 浏览器,访问 URLhttp://localhost:8080/swagger即可查看 Swagger UI。

您现在应该看到类似这样的内容:

展开/author/signup端点,并修改请求主体/json 使其如下所示(单击Try It Out按钮即可执行此操作):



{
  "FirstName": "Johnny",
  "LastName": "Lawrence",
  "Email": "what@is.uber",
  "UserName": "EagleFang",
  "Password": "death2kobra"
}


Enter fullscreen mode Exit fullscreen mode

在执行请求之前,请前往Endpoint.cs文件并在第 14 行设置断点。然后继续并点击 Swagger 中的“执行”按钮。断点触发后,检查该HandleAsync()方法的请求 DTO 参数,您将看到类似以下内容:

这基本上就是你从客户端(在本例中是 Swagger UI)接收请求的方式。处理程序方法会从传入的 http 请求中获取一个完整填充的 POCO。有关此模型绑定工作原理的详细说明,请参阅此处的文档页面。

现在让我们从端点返回响应HandleAsync()。停止调试并更新方法,如下所示:



public override async Task HandleAsync(Request r, CancellationToken c)
{
    await SendAsync(new Response()
    {
        Message = $"hello {r.FirstName} {r.LastName}! your request has been received!"
    });
}


Enter fullscreen mode Exit fullscreen mode

再次启动应用程序并在 Swagger UI 中执行相同的请求。服务器的响应应该显示如下:

多种方法可以将响应从处理程序发送回客户端。这里我们发送一个填充了自定义消息的响应 DTO 的新实例。

输入验证

打开Models.cs文件并使验证器类如下所示:



public class Validator : Validator<Request>
{
    public Validator()
    {
        RuleFor(x => x.FirstName)
            .NotEmpty().WithMessage("your name is required!")
            .MinimumLength(3).WithMessage("name is too short!")
            .MaximumLength(25).WithMessage("name is too long!");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("email address is required!")
            .EmailAddress().WithMessage("the format of your email address is wrong!");

        RuleFor(x => x.UserName)
            .NotEmpty().WithMessage("a username is required!")
            .MinimumLength(3).WithMessage("username is too short!")
            .MaximumLength(15).WithMessage("username is too long!");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("a password is required!")
            .MinimumLength(10).WithMessage("password is too short!")
            .MaximumLength(25).WithMessage("password is too long!");
    }
}


Enter fullscreen mode Exit fullscreen mode

这里我们使用Fluent Validation规则定义输入验证要求。让我们看看当用户输入不符合上述条件时会发生什么。在 Swagger 中执行相同的请求,并使用以下错误的 JSON 内容:



{
  "LastName": "Lawrence",
  "Email": "what is email?",
  "UserName": "EagleFang",
  "Password": "123"
}


Enter fullscreen mode Exit fullscreen mode

服务器将会做出如下响应:



{
  "StatusCode": 400,
  "Message": "One or more errors occured!",
  "Errors": {
    "FirstName": [ "your name is required!" ],
    "Email": [ "the format of your email address is wrong!" ],
    "Password": ["password is too short!" ]
  }
}


Enter fullscreen mode Exit fullscreen mode

如您所见,如果传入的请求数据不符合验证标准,http 400则会返回错误响应,其中包含错误的详细信息。如果传入请求中存在验证错误,处理程序逻辑将不会执行。如果需要,可以像这样更改此默认行为。

处理程序逻辑

让我们继续Author通过修改处理程序逻辑将新实体持久保存到数据库中。



public override async Task HandleAsync(Request r, CancellationToken c)
{
    var author = Map.ToEntity(r);

    var emailIsTaken = await Data.EmailAddressIsTaken(author.Email);

    if (emailIsTaken)
        AddError(r => r.Email, "Sorry! Email address is already in use...");

    var userNameIsTaken = await Data.UserNameIsTaken(author.UserName);

    if (userNameIsTaken)
        AddError(r => r.UserName, "Sorry! Ehat username is not available...");

    ThrowIfAnyErrors();

    await Data.CreateNewAuthor(author);

    await SendAsync(new()
    {
        Message = "Thank you for signing up as an author!"
    });
}


Enter fullscreen mode Exit fullscreen mode

首先,我们使用端点类属性ToEntity()上的方法Map将请求 dto 转换为Author 域实体。映射的逻辑位于此处的Mapper.cs文件中。您可以在此处阅读有关映射器类的更多信息

然后,我们询问数据库此邮箱地址是否已被他人占用(代码见此处)。如果已被占用,我们将使用该方法将验证错误添加到端点的错误集合中AddError()

接下来,我们询问数据库该用户名是否已被他人使用,如果已被使用则添加错误。

所有业务规则检查完成后,如果之前的任何业务规则检查失败,我们希望向客户端发送错误响应。这就是 的作用ThrowIfAnyErrors()。当用户名或电子邮件地址被获取时,将向客户端发送类似以下的响应。执行会在此停止,并且不会执行后续代码行。



{
  "StatusCode": 400,
  "Message": "One or more errors occured!",
  "Errors": {
    "Email": [ "sorry! email address is already in use." ],
    "UserName": [ "sorry! that username is not available." ]
  }
}


Enter fullscreen mode Exit fullscreen mode

如果没有添加验证错误,并且作者创建成功,客户端将收到以下 json 响应。



{
"Message": "Thank you for signing up as an author!"
}

Enter fullscreen mode Exit fullscreen mode




恭喜!

到目前为止,您已经坚持不懈,并拥有了第一个可以正常工作的端点。如果您有兴趣完成此练习,请前往 GitHub 查看完整源代码。现在应该已经不言自明了。如果您有任何不清楚的地方,请在此处评论或创建GitHub 问题。我会尽力在 24 小时内回复。此外,您还可以查看以下资源,它们将解释大部分代码。

资源

文章来源:https://dev.to/djnitehawk/building-rest-apis-in-net-6-the-easy-way-3h0d
PREV
11 个 Web 开发者的 UI 设计技巧
NEXT
关于 SSH 我想要记住的事情