FastEndpoints 构建高性能的 Api

前言

FastEndpoints 是一个轻量级的 REST API 开发框架,它摒弃了传统的 Controller/Action 模式,是基于REPR(Request-Endpoint-Response)设计模式设计的API。性能与Minimal APIs 不相上下。对比传统 MVC 性能上要上不少。对于性能的要求高的微服务非常推荐。

REPR 设计模式

FastEndpoints 摒弃 MVC 的「Controller(控制器)- Action(动作方法)」分层,采用 REPR(Request-Endpoint-Response)模式,将 API 的核心逻辑收敛到单一「Endpoint(端点)」类中,结构更紧凑:

  1. Request(请求):强类型的请求模型(如 CreateUserRequest),负责接收和验证 HTTP 请求参数(路径、查询、Body 等),内置数据验证(支持 DataAnnotations、FluentValidation 等)。
  2. Endpoint(端点):API 的核心逻辑载体,一个类对应一个 API 端点(如 CreateUserEndpoint),需实现 IEndpoint 接口或继承 Endpoint<TRequest, TResponse> 基类,重写 HandleAsync 方法处理业务逻辑。
  3. Response(响应):强类型的响应模型(如 CreateUserResponse),负责定义 API 的返回结构。

这种模式的核心价值在于 边界清晰:每个交互流程都被封装为独立的 “请求 - 端点 - 响应” 单元,避免业务逻辑与数据传输逻辑混杂,同时让接口的 “调用规则” 可被快速理解(无需阅读复杂代码,仅需看请求 / 响应定义即可)。

FastEndpoints基本使用

通过 NuGet 安装核心包:

1
Install-Package FastEndpoints

在 Program.cs 中进行简单配置即可启用 FastEndpoints:

1
2
3
4
5
6
7
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints(); // 注册FastEndpoints服务

var app = builder.Build();
app.UseFastEndpoints(); // 使用FastEndpoints中间件
app.Run();

创建一个简单的 “Hello World” 端点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using FastEndpoints;

namespace WebApplication1;

public class HelloEndpoint : Endpoint<HelloRequest, HelloResponse>
{
public override void Configure()
{
Get("/api/hello"); // 配置HTTP方法和路由
AllowAnonymous(); // 允许匿名访问
}

public override async Task HandleAsync(HelloRequest req, CancellationToken ct)
{
await Send.OkAsync(new HelloResponse
{
Message = $"Hello, {req.Name}!",
Timestamp = DateTime.Now
}, ct);
}
}

// 定义请求DTO
public class HelloRequest
{
public string Name { get; set; }
}

// 定义响应DTO
public class HelloResponse
{
public string Message { get; set; }
public DateTime Timestamp { get; set; }
}

如果路由简单还可以通过Attribute属性标识的方式来申明。

1
2
3
4
5
6
7
8
9
10
11
12
13
[HttpGet("api/hello")]
[AllowAnonymous]
public class HelloEndpoint : Endpoint<HelloRequest, HelloResponse>
{
public override async Task HandleAsync(HelloRequest req, CancellationToken ct)
{
await Send.OkAsync(new HelloResponse
{
Message = $"Hello, {req.Name ?? "1"}!",
}, cancellation: ct);
}
}

Endpoint 是 FastEndpoints 的核心,每个端点类都继承自Endpoint<TRequest, TResponse>抽象类

  • TRequest:表示请求数据的 DTO
  • TResponse:表示响应数据的 DTO

每个端点需要实现两个方法:

  • Configure():配置路由、HTTP 方法、权限等元数据
  • HandleAsync():处理请求的核心逻辑

默认提供了四种端点

  • Endpoint - 只需要请求模型(TRequest),但不需要响应模型(无返回体,通常仅返回状态码)。
  • Endpoint<TRequest, TResponse> - 既需要请求模型(TRequest),也需要响应模型(TResponse)。
  • EndpointWithoutRequest - 不需要请求模型(无输入参数),也不需要响应模型(无返回体)。
  • EndpointWithoutRequest - 不需要请求模型(无输入参数),但需要响应模型(TResponse,返回具体数据)。

Endpoint、EndpointWithoutRequest 并不限制返回数据,还是可以返回任何可序列化的数据。但是不建议这么使用。

EndpointWithoutRequest、EndpointWithoutRequest 虽然不接受输入参数,但是还是可以使用过 Route、 和Query 获取参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[HttpGet("api/hello/{id}")]
[AllowAnonymous]
public class HelloEndpoint : EndpointWithoutRequest
{
public override async Task HandleAsync(CancellationToken ct)
{
var id = Route<int>("id");
var name = Query<string>("name");
await Send.OkAsync(new HelloResponse
{
Message = $"Hello, {name},{id}!",
});
}
}

或者使用EmptyRequest 和 EmptyResponse 定义端点

1
public class MyEndpoint : Endpoint<EmptyRequest,EmptyResponse> { }

路由配置

在 FastEndpoints 中,路由配置主要在端点类的Configure()方法中完成。

1
2
3
4
5
public override void Configure()
{
Get("/api/hello");
Post("/api/hello");
}

对于需要支持多种 HTTP 方法的端点,可以使用Verbs()方法:

1
2
3
4
5
public override void Configure()
{
Routes("/api/hello","/api/hello1");
Verbs(Http.GET, Http.POST);
}

可以为所有端点配置全局路由前缀,在 Program.cs 中设置:

1
2
3
4
builder.Services.AddFastEndpoints(options =>
{
options.RoutePrefix = "api/v1"; // 所有端点都会加上这个前缀
});

还可以为特定端点或一组端点设置局部前缀:

1
2
3
4
5
6
public override void Configure()
{
Routes("/api"); // 局部前缀
Get("/hello"); // 完整路由为 /api/v1/api/hello(结合全局前缀)
AllowAnonymous();
}

Swagger 集成

需要安装指定的包

1
dotnet add package FastEndpoints.Swagger

在 Program.cs 中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using FastEndpoints;
using FastEndpoints.Swagger;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFastEndpoints() // 注册FastEndpoints服务
.SwaggerDocument(); // 添加Swagger支持

var app = builder.Build();
app.UseFastEndpoints() // 使用FastEndpoints中间件
.UseSwaggerGen(); // 启用Swagger生成

app.Run();

FastEndpoints 提供了 Summary() 方法,用于在 Configure() 中为指定端点添加描述文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public override void Configure()
{
Get("api/hello");

Description(b => b
.Produces(403)
.Produces(500)
);
Summary(s => {
s.Summary = " 用于提供端点的简短描述";
s.Description = "提供更详细的端点说明,可以包含功能细节、使用场景、注意事项等";
s.ExampleRequest = new HelloRequest { Name = "example name" };
s.Responses[200] = "成功响应描述";
s.Responses[403] = "禁止访问描述";
s.Responses[500] = "错误描述";
});

AllowAnonymous();
}

模型绑定

对于需要路由参数的可以使用大括号{}定义,用于从 URL 路径中提取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public override void Configure()
{
Get("/api/hello/{name}");
// 字符串长度约束
Get("/api/hello/{name:length(1,2)}");
// 整数约束
Get("/api/hello/{name:int}");
// 正则约束
Get("/api/hello/{name:regex(^\\d{4}-\\d{4}$)}");
// 布尔值约束
Get("/api/hello/{name:bool}");
// 可选参数
Get("/api/hello/{name?}");
}

参数会自动绑定到请求 DTO 中同名的属性:

1
2
3
4
public class HelloRequest
{
public string Name { get; set; }
}

对于 POST、PUT、PATCH 等 HTTP 方法,请求数据通常放在请求体中(如 JSON、XML 或表单数据)。当 HTTP 方法为 POST、PUT、PATCH 时,自动从请求体读取数据,默认支持 JSON 格式(Content-Type: application/json)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 客户端发送 POST 请求,Content-Type: application/json
// 请求体: {"name":"iPhone 15","price":7999.99,"inStock":true}
public class CreateProductRequest
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
}
// 端点配置
public override void Configure()
{
Post("/products");
}

对于传统表单提交(Content-Type: application/x-www-form-urlencoded 或 multipart/form-data),使用 [FormField] 特性指定绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UploadProfileRequest
{
[FormField] // 绑定表单字段
public string Username { get; set; }

[FormField] // 绑定表单字段
public IFormFile? Avatar { get; set; } // 上传文件
}

// 端点配置
public override void Configure()
{
Post("/profile/upload");
AllowFileUploads(); // 允许文件上传
}

可以从 HTTP 请求头中绑定数据,使用 [FromHeader] 特性显式指定。

1
2
3
4
5
6
7
8
9
10
11
public class AuthenticatedRequest
{
// 绑定 Authorization 请求头
[FromHeader("Authorization")]
public string? AuthToken { get; set; }

// 绑定自定义请求头 X-Request-Id
[FromHeader("X-Request-Id")]
public string? RequestId { get; set; }
}

一个 DTO 可以同时从多个数据源绑定数据(路由 + 查询字符串 + 请求头 + 请求体),框架会自动从相应来源获取并组合数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 客户端请求: PUT /users/123?includeDetails=true
// 请求头: X-Request-Version: 2
// 请求体: {"name":"John Doe","email":"john@example.com"}
public class UpdateUserRequest
{
// 来自路由参数
[FromRoute] // 也可是显示的申明参数的来源
public int Id { get; set; }

// 来自查询字符串
public bool IncludeDetails { get; set; }

// 来自请求头
[FromHeader("X-Request-Version")]
public int Version { get; set; }

// 来自请求体
public string Name { get; set; }
public string Email { get; set; }
}

// 端点配置
public override void Configure()
{
Post("/users/{id}");
}

如果希望某些属性不参与模型绑定(如只读属性),可以使用 [BindNever] 特性:

1
2
3
4
5
6
7
8
9
public class CreateUserRequest
{
public string Username { get; set; }
public string Password { get; set; }

// 忽略绑定
[BindNever]
public Guid UserId { get; set; }
}

模型验证

FastEndpoints 模型验证是基于 FluentValidation 的,FluentValidation是一个基于 .NET 的流畅式验证库,支持通过链式语法定义强类型的验证规则,广泛用于验证实体类、DTO(数据传输对象)等数据的合法性,相比传统的注解式验证更灵活、可维护性更强。

  • Validator:验证器基类,所有自定义验证器都需继承它,泛型 T 是待验证的目标类型(如 DTO、实体类)。
  • RuleFor():验证规则的入口方法,用于指定要验证的属性(如 RuleFor(x => x.Name) 表示验证 T 类型的 Name 属性),后续通过链式方法追加具体验证逻辑(如非空、长度限制等)。
1
2
3
4
5
6
7
8
9
// 注册用户的 DTO
public class RegisterUserDto
{
public string UserName { get; set; } // 用户名
public string Email { get; set; } // 邮箱
public int Age { get; set; } // 年龄
public string Password { get; set; } // 密码
public string ConfirmPassword { get; set; } // 确认密码
}

创建自定义验证器继承 AbstractValidator,在构造函数中通过 RuleFor() 定义验证规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using FluentValidation;

// 针对 RegisterUserDto 的验证器
public class RegisterUserDtoValidator : Validator<RegisterUserDto>
{
public RegisterUserDtoValidator()
{
RuleFor(dto => dto.UserName)
.NotNull().WithMessage("用户名不能为空")
.Length(2, 20).WithMessage("用户名长度需在 2-20 个字符之间");

RuleFor(dto => dto.Email)
.NotEmpty().WithMessage("邮箱不能为空")
.EmailAddress().WithMessage("请输入合法的邮箱格式")
.MaximumLength(100).WithMessage("邮箱长度不能超过 100 字符");

RuleFor(dto => dto.Age)
.InclusiveBetween(18, 60).WithMessage("年龄需在 18-60 岁之间")
.NotNull().WithMessage("年龄不能为空");

RuleFor(dto => dto.Password)
.NotEmpty().WithMessage("密码不能为空")
.MinimumLength(8).WithMessage("密码长度不能少于 8 字符")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$")
.WithMessage("密码需包含大小写字母和数字");

RuleFor(dto => dto.ConfirmPassword)
.Equal(dto => dto.Password).WithMessage("两次输入的密码不一致")
.NotEmpty().WithMessage("请确认密码");
}
}

对于简单的输入验证,可以在请求 DTO 上使用 DataAnnotations 来实现。但是一些复杂的的还是建议使用 FluentValidation 来实现。性能方面 DataAnnotations 会比 DataAnnotations 稍快,但是基本可以忽略不计。

前后置处理器

前置 / 后置处理器(IPreProcessor/IPostProcessor)是 FastEndpoints 提供的横切逻辑处理机制核心目标是减少代码重复、实现关注点分离。

IPreProcessor 是用于在端点 HandleAsync 方法执行之前运行自定义逻辑的接口。它专注于请求处理前的准备工作,例如参数验证增强、请求数据转换、资源预热等。

1
2
3
4
5
6
7
8
9
10
11
public class RequestLoggingPreProcessor<TRequest> : IPreProcessor<TRequest>
{
public Task PreProcessAsync(IPreProcessorContext<TRequest> context, CancellationToken ct)
{
var logger = context.HttpContext.Resolve<ILogger<TRequest>>();
var requestJson = JsonSerializer.Serialize(context.Request);
logger.LogInformation("请求参数: {RequestJson}", requestJson);
return Task.CompletedTask;
}
}

前置处理器可以通过两种方式应用到端点,第一种在端点的 Configure 方法中,使用 PreProcessors 方法指定当前端点要使用的前置处理器。

1
2
3
4
5
6
public override void Configure()
{
Get("/health");
AllowAnonymous();
PreProcessors(new RequestLoggingPreProcessor<EmptyRequest>());
}

或者使用创建一个基础端点要其他端点继承该端点

1
2
3
4
5
6
7
8
9
10
public abstract class BaseCrudEndpoint<TRequest, TResponse> : Endpoint<TRequest, TResponse>
{
public override void Configure()
{
// 所有继承该类的端点都会自动应用这些处理器
PreProcessors(
new RequestLoggingPreProcessor<TRequest>()
);
}
}

IPostProcessor<TRequest, TResponse> 接口用于在端点的 HandleAsync 方法执行之后(响应发送之前)运行自定义逻辑。它适合处理响应日志记录、数据格式化、资源清理等操作。后置处理器还可以访问未处理的异常。

1
2
3
4
5
6
7
8
9
10
11
12
public class MyResponseLogger<TRequest, TResponse> : IPostProcessor<TRequest, TResponse>
{
public Task PostProcessAsync(IPostProcessorContext<TRequest, TResponse> ctx, ...)
{
var logger = ctx.HttpContext.Resolve<ILogger<TResponse>>();

if (ctx.Response is CreateSaleResponse response)
logger.LogWarning($"sale complete: {response.OrderID}");

return Task.CompletedTask;
}
}

如果想创建全局处理器可以使用 实现 IGlobalPreProcessor 和 IGlobalPostProcessor 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 前置
public class RequestLoggingPreProcessor : IGlobalPreProcessor
{
public Task PreProcessAsync(IPreProcessorContext context, CancellationToken ct)
{
var logger = context.HttpContext.Resolve<ILogger<RequestLoggingPreProcessor>>();
var requestJson = JsonSerializer.Serialize(context.Request);
logger.LogInformation("请求参数: {RequestJson}", requestJson);
return Task.CompletedTask;
}
}
// 后置
public Task PostProcessAsync(IPostProcessorContext context, CancellationToken ct)
{
var logger = context.HttpContext.Resolve<ILogger<ResponseLoggingPostProcessor>>();
var responseJson = JsonSerializer.Serialize(context.Response);
logger.LogInformation(
"响应结果 - 状态码: {StatusCode}, 响应内容: {ResponseJson}",
context.HttpContext.Response.StatusCode,
responseJson
);

return Task.CompletedTask;
}

在项目启动时进行基础配置

1
2
3
4
5
6
7
8
9
10
11
12
var app = builder.Build();
app.UseFastEndpoints(c => {
c.Endpoints.Configurator = ep => {
ep.DontAutoSendResponse();
ep.PreProcessor<RequestLoggingPreProcessor>(Order.Before); // 前置
ep.PostProcessor<ResponseLoggingPostProcessor>(Order.After);// 后置
ep.AllowAnonymous();
};
})
.UseSwaggerGen();

app.Run();

总结

FastEndpoints 的能力远不止前文覆盖的 “基础功能”—— 它还提供了 Pub/Sub 消息订阅、Command Bus 命令总线等进阶特性。并提供了丰富的可配置性,可以通过 FastEndpoints 官方文档 查阅。


FastEndpoints 构建高性能的 Api
http://example.com/posts/33671.html
作者
她微笑的脸y
发布于
2025年8月26日
许可协议