路由注册 (RegisterRoutes)
RegisterRoutes 是插件定义对外 API 接口的核心方法。它在主程序启动前被调用,这是插件最核心的部分,用于建立Api访问与代码处理的映射关系。
路由前缀管理
为了规范 API 结构并避免插件冲突,SharwAPI 提供了两种路由管理模式。
自动前缀模式 (推荐)
这是新版插件 (版本>v0.2.0) 的首选模式。主程序会自动为你的插件创建一个标准化的路由组(/{插件名},如/myplugin),并将该组传递给 RegisterRoutes 方法。
开启方式:在插件类中重写 UseAutoRoutePrefix 属性为 true 即可。
public class MyPlugin : IApiPlugin
{
public string Name => "sharw.demo";
// 开启自动前缀:主程序会自动帮你创建 /sharw.demo 路由组
public bool UseAutoRoutePrefix => true;
// 实现IApiPlugin的其他东西...
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// 最终地址: GET /sharw.demo/hello
app.MapGet("/hello", () => "Hello World");
}
}手动模式 (兼容)
这是默认模式(UseAutoRoutePrefix 默认为 false)。此时主程序传递的是 WebApplication(根路由构建器),你需要手动管理前缀。但仅用于老旧插件或需要完全自定义路由结构的特殊场景。
这也意味着你拿到的是全局路由上下文:如果不手动 MapGroup 做前缀隔离,路由会直接挂到根路径,容易与其他插件或主程序接口冲突。
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// 你必须手动创建路由组,否则会污染根路径
// MapGroup()可以用来追加路径
var group = app.MapGroup("/brrr").MapGroup("/hallo");
// 最终地址: GET /brrr/hallo/bonjour
group.MapGet("/bonjour", () => "Hello World");
}感知路由前缀更改 (OnRoutePrefixResolved)
当 UseAutoRoutePrefix 为 true 时,运维人员可以通过 appsettings.json 中的 RouteOverride 节将你的路由前缀覆盖为其他值(详见应用配置)。如果你的插件需要感知自己最终生效的路由前缀(例如用于日志输出或自描述接口),可以重写 OnRoutePrefixResolved 方法:
public class MyPlugin : IApiPlugin
{
private string _resolvedPrefix = string.Empty;
public string Name => "myplugin";
public bool UseAutoRoutePrefix => true;
// 路由前缀确定后,主程序会自动调用此方法(仅 UseAutoRoutePrefix = true 时触发)
public void OnRoutePrefixResolved(string resolvedPrefix, bool isOverridden)
{
_resolvedPrefix = resolvedPrefix;
if (isOverridden)
{
// 前缀被运维人员通过 RouteOverride 覆盖
Console.WriteLine($"路由前缀已被覆盖为: {resolvedPrefix}");
}
}
}| 参数 | 说明 |
|---|---|
resolvedPrefix | 最终生效的路由前缀(不含前导斜杠,如 myplugin) |
isOverridden | 前缀是否来自 RouteOverride 配置(而非插件默认的 Name) |
TIP
此方法含有默认空实现,不重写它不会对插件的正常运行产生任何影响。
常用操作
以下示例均基于 自动前缀模式 (UseAutoRoutePrefix => true),并假设插件名为myplugin
基础接口定义
利用 Minimal API 语法,你可以快速定义各种 HTTP 方法的接口。
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// GET /api/myplugin/status
app.MapGet("/status", () => "Running");
// POST /api/myplugin/data
app.MapPost("/data", (MyData data) =>
{
return Results.Ok($"Received: {data.Content}");
});
}使用工具 (服务注入)
主程序会将在 RegisterServices 中注册的工具(服务)注入进来,可以在这里直接使用。你只需要在处理函数的参数列表中声明需要的类型。
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// 场景:需要使用 HttpClient 访问外部网络
// 动作:在参数中声明 IHttpClientFactory
// 依赖注入会自动把依赖注入进来
app.MapGet("/proxy", async (IHttpClientFactory clientFactory) =>
{
var client = clientFactory.CreateClient("baidu");
return await client.GetStringAsync("/");
});
}为什么需要 IHttpClientFactory?
你可能会好奇:为什么不能直接注入 HttpClient?
直接使用单例的 HttpClient 可能会导致 DNS 更新不生效(当目标 IP 变更时无法感知)。同时频繁创建新的 HttpClient 则会导致 Socket 端口耗尽。
IHttpClientFactory 是 ASP.NET Core 专门设计的解决方案:
- 智能管理:它在后台维护连接池,既能复用连接(避免端口耗尽),又能定期刷新(感知 DNS 变更)。
- 按需生产:你注册的 "baidu" 或 "google" 只是配置模板。通过工厂,你可以随时生产出配置好的、独立的客户端实例,互不干扰。
处理请求参数
Minimal API 支持自动绑定 URL 参数、查询字符串和请求体 (Body)。
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// 1. 路径参数: GET /api/myplugin/users/101
app.MapGet("/users/{id}", (int id) => $"查找 ID 为 {id} 的用户");
// 2. 查询参数: GET /api/myplugin/search?keyword=sharw
app.MapGet("/search", (string keyword) => $"搜索关键字: {keyword}");
// 3. 请求体 (JSON): POST /api/myplugin/users
// 主程序会自动将 JSON 数据反序列化为 UserDto 对象
app.MapPost("/users", (UserDto user) =>
{
return $"收到用户: {user.Name}";
});
}权限控制与局部拦截
如果你需要保护某个接口,或者仅针对特定接口执行逻辑(而非全局拦截),可以使用以下方法。
public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
// 权限控制:仅允许通过身份验证的请求访问
app.MapGet("/secret", () => "机密数据")
.RequireAuthorization();
// 局部拦截 (过滤器):仅针对该路由执行的前置/后置逻辑
app.MapGet("/filtered", () => "数据")
.AddEndpointFilter(async (context, next) =>
{
// 接口执行前
Console.WriteLine("即将执行接口...");
var result = await next(context);
// 接口执行后
Console.WriteLine("接口执行完毕");
return result;
});
}注意事项
异步处理
如果你的业务逻辑涉及 I/O 操作(如读写数据库、文件操作、HTTP 请求),请务必使用 async/await 模式。
- ❌错误:
app.MapGet("/", (Db db) => db.Data.ToList());(这会阻塞主线程) - ✔️正确:
app.MapGet("/", async (Db db) => await db.Data.ToListAsync());
避免使用 Controller
尽管主程序在技术上兼容传统的 Controller 写法,但在插件开发中,并不推荐使用它。因为 Controller 会引入复杂的程序集扫描问题和额外的资源开销,而 Minimal API 是专为这种轻量级集成设计的首选方案。
注册顺序
插件的路由注册是在中间件配置之后进行的。这意味着所有在 Configure 中注册的中间件都会对这些路由生效。