FluentOpenApi 是一个轻量级、流畅的框架,用于定义 OpenApi 架构(Schema)并集成数据验证功能。它通过链式调用的方式简化了 API 模型的定义,同时支持与 ASP.NET Core 无缝集成。本文档将介绍如何使用 FluentOpenApi,如何将其集成到 ASP.NET Core 项目中,以及如何进行进阶扩展。
- 优点:流畅的 API、易于集成、可扩展性强。
- 适用场景:需要规范化 API 定义和验证的 ASP.NET Core 项目。
FluentOpenApi 已发布到 NuGet.org,你可以通过以下方式安装到你的 .NET 项目中。
通过 NuGet 包管理器 打开你的项目(例如在 Visual Studio 中)。 右键项目,选择 Manage NuGet Packages。 在搜索框中输入 FluentOpenApi。 选择最新版本(例如 0.1.0),点击 Install。
FluentOpenApi 通过 ModelSchema<T> 基类和链式调用方法定义模型的架构规则。以下是一个简单的例子:
public class Person
{
public string? Name { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
public string[]? Items { get; set; }
}
public class PersonSchema : ModelSchema<Person>
{
public PersonSchema()
{
For(x => x.Name)
.Required()
.RegularExpression(@"^[a-zA-Z\s]+$")
.MinLength(2)
.MaxLength(50)
.WithDescription("Person's full name")
.WithDefault("John Doe");
For(x => x.Age)
.Range(0, 150);
For(x => x.Email)
.Required()
.WithDescription("Contact email");
For(x => x.Items)
.ItemsRange(1, 5);
}
}For:指定模型的属性。- 链式方法:如
Required()、RegularExpression()、MinLength()等,用于定义规则和验证条件。
- 规则(Rules):描述属性的元数据,例如是否必填、默认值等。
- 验证器(Validators):执行数据验证逻辑,例如检查长度或正则匹配。
- 流畅接口:通过链式调用配置属性规则。
FluentOpenApi 提供了与 ASP.NET Core 的集成支持,可以自动生成 OpenApi 文档并执行请求验证。以下是集成步骤:
在 Program.cs 中配置 FluentOpenApi 和 OpenApi 服务:
var builder = WebApplication.CreateBuilder(args);
// 注册 FluentOpenApi 和 Schema
builder.Services.AddFluentOpenApi(o =>
{
o.AddSchema<PersonSchema>();
});
// 配置 OpenApi 并添加 Schema 转换器
builder.Services.AddOpenApi(o =>
{
o.AddFluentSchemaTransformer();
});
var app = builder.Build();
// 启用 OpenApi 端点
app.MapOpenApi();
app.MapScalarApiReference();
// 定义 API 端点并启用验证
app.MapPost("/person", (Person person) => Results.Ok(person))
.WithValidation();
app.Run();AddFluentOpenApi:- 注册
FluentOpenApiProvider和ModelSchema实例。 - 添加验证过滤器(
ValidationEndpointFilter)以支持.WithValidation()。
- 注册
AddFluentSchemaTransformer:- 将
FluentOpenApi的规则应用到 OpenApi 文档中,例如设置必填字段、描述和默认值。
- 将
- 端点验证:
- 使用
.WithValidation()启用自动验证,基于PersonSchema中的规则检查请求数据。
- 使用
发送以下请求:
{
"Name": "123",
"Age": 200,
"Email": null,
"Items": ["a", "b", "c", "d", "e", "f"]
}响应将是验证错误,例如:
{
"Name": [
"Name has invalid format"
],
"Age": [
"Age must be between 0 and 150"
],
"Email": [
"Email cannot be null"
]
}FluentOpenApi 支持自定义规则和验证器,以满足复杂需求。
创建一个新的规则,例如 EmailRule:
public class EmailRule : SchemaRule
{
public override void Apply(OpenApiSchema schema)
{
schema.Format = "email";
}
}创建一个对应的验证器,例如 EmailValidator:
public class EmailValidator : Validator
{
private static readonly Regex EmailRegex = new Regex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled);
public override Func<object?, bool> GetCondition()
{
return value => value == null || !EmailRegex.IsMatch(value.ToString()!);
}
public override string GetErrorMessage(string propertyName)
{
return $"{propertyName} must be a valid email address";
}
}在 PersonSchema 中使用:
public class PersonSchema : ModelSchema<Person>
{
public PersonSchema()
{
For(x => x.Email)
.Required()
.WithDescription("Contact email")
.AddRule(new EmailRule())
.WithValidation(new EmailValidator());
}
}AddRule:添加自定义规则。WithValidation:绑定自定义验证器。
你可以通过扩展 SchemaExtensions 类添加自定义链式方法。
例如,添加 Email() 方法:
public static class SchemaExtensions
{
// 已有方法省略...
public static PropertyRuleBuilder<T, string> Email<T>(
this PropertyRuleBuilder<T, string> builder) where T : class
{
return builder.AddRule(new EmailRule()).WithValidation(new EmailValidator());
}
}更新 PersonSchema:
public class PersonSchema : ModelSchema<Person>
{
public PersonSchema()
{
For(x => x.Name)
.Required()
.Matches(@"^[a-zA-Z\s]+$")
.MinLength(2)
.MaxLength(50)
.WithDescription("Person's full name")
.WithDefault("John Doe");
For(x => x.Age)
.Range(0, 150);
For(x => x.Email)
.Required()
.Email() // 使用自定义扩展方法
.WithDescription("Contact email");
For(x => x.Items)
.RangeForArray(1, 5);
}
}Email()封装了EmailRule和EmailValidator,简化配置。
支持复杂类型的默认值:
public static PropertyRuleBuilder<T, TProperty> WithDefaultComplex<T, TProperty>(
this PropertyRuleBuilder<T, TProperty> builder, Func<TProperty> defaultFactory) where T : class
{
return builder.AddRule(new DefaultRule(ToOpenApiAny(defaultFactory())));
}使用:
For(x => x.Items)
.RangeForArray(1, 5)
.WithDefaultComplex(() => new[] { "item1", "item2" });