MiniController 2.0.0
dotnet add package MiniController --version 2.0.0
NuGet\Install-Package MiniController -Version 2.0.0
<PackageReference Include="MiniController" Version="2.0.0" />
<PackageVersion Include="MiniController" Version="2.0.0" />
<PackageReference Include="MiniController" />
paket add MiniController --version 2.0.0
#r "nuget: MiniController, 2.0.0"
#:package MiniController@2.0.0
#addin nuget:?package=MiniController&version=2.0.0
#tool nuget:?package=MiniController&version=2.0.0
MiniController
MiniController 是一个基于 Roslyn Incremental Source Generator 的 ASP.NET Core Minimal API 组织工具。它使用 attribute 声明路由、绑定、授权和 OpenAPI 元数据,并在编译期生成 endpoint 注册代码。
运行基线
- 生产样例与 CI 使用 .NET 10。
- Generator 和 Attributes 包保持
netstandard2.0。 - 仓库通过
global.json固定已验证的稳定 SDK。
安装
dotnet add package MiniController --version 2.0.0
dotnet add package MiniController.Attributes --version 2.0.0
<ItemGroup>
<PackageReference Include="MiniController" Version="2.0.0" PrivateAssets="all" />
<PackageReference Include="MiniController.Attributes" Version="2.0.0" />
</ItemGroup>
MiniController 是 analyzer-only 包,不会成为应用的运行时依赖。
快速开始
using Microsoft.AspNetCore.Mvc;
using MiniController.Attributes;
namespace MyApi;
[MiniController("/api/products")]
public sealed class ProductController
{
[HttpGet("{id:int}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
public IResult GetProduct(
[FromRoute(Name = "id")] int productId,
[FromQuery(Name = "include-details")] bool includeDetails = false)
{
return TypedResults.Ok(new Product(productId, includeDetails));
}
}
public sealed record Product(int Id, bool IncludeDetails);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMiniControllers();
var app = builder.Build();
app.MapMiniController();
app.Run();
实例 controller 自动以 Transient 生命周期注册;静态类不进入 DI。
路由
显式路由
[MiniController("/api/orders")]
public static class OrderEndpoints
{
[HttpGet("{id:int}")]
public static IResult GetOrder(int id) => TypedResults.Ok(id);
}
名称推断
省略 HTTP attribute 时,可按方法名前缀推断:
Get*→ GETPost*、Create*、Add*→ POSTPut*、Update*→ PUTDelete*、Remove*→ DELETEPatch*、Head*、Options*→ 对应 method
默认 controller 路由会移除 Controller、Service、Endpoint、Endpoints 后缀并转为 kebab-case。例如 OrderHistoryController 对应 /api/order-history。
路由 token
支持 [area]、[controller]、[action]:
[Area("Admin")]
[MiniController("/api/[area]/[controller]/[action]")]
public static class ReportEndpoints
{
[HttpGet("weekly")]
public static IResult GetWeeklyReport() => TypedResults.Ok();
}
上例路由为 GET /api/admin/report/weekly。当模板包含 [action] 时,显式 method template 是 action token 的值,不会再次追加到路径。
继承
抽象基类可以定义共享 endpoint,但不会自身注册:
[MiniController("/api/[controller]")]
public abstract class BaseController
{
[HttpGet("health")]
public virtual IResult GetHealth() => TypedResults.Ok();
}
public sealed class StatusController : BaseController;
派生类的 override/new 声明优先。两个具体类型若形成相同 HTTP method 与 route,编译器会报告 MC001 Error,并定位两个冲突方法。
开放泛型 controller 或嵌套在开放泛型中的 controller 会报告 MC002。
参数绑定
实例方法包装器保留以下信息:
[FromServices][FromRoute(Name = "...")][FromQuery(Name = "...")][FromHeader(Name = "...")][FromBody][FromForm(Name = "...")]- nullable 标注和显式默认值
[HttpGet("search")]
public IResult Search(
[FromQuery(Name = "q")] string keyword,
[FromHeader(Name = "X-Tenant")] string tenant,
[FromQuery] int page = 1)
{
return TypedResults.Ok(new { keyword, tenant, page });
}
授权
类级和方法级的多个 [Authorize] 会全部保留,并按 ASP.NET Core 的组合规则执行。Roles、Policy 和 AuthenticationSchemes 会作为 AuthorizeAttribute 元数据生成。
[MiniController("/api/security")]
[Authorize(Policy = "tenant")]
public sealed class SecurityController
{
[HttpGet("admin")]
[Authorize(Roles = "Admin", AuthenticationSchemes = "Bearer")]
public IResult GetAdmin() => TypedResults.Ok();
[HttpGet("public")]
[AllowAnonymous]
public IResult GetPublic() => TypedResults.Ok();
}
应用仍需配置 authentication、authorization middleware 和 policy。
Endpoint filters
使用可重复的 [MiniControllerFilter] 添加类级或方法级 filter:
public sealed class LoggingFilter : IEndpointFilter
{
public ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next) => next(context);
}
[MiniController("/api/filtered")]
[MiniControllerFilter(typeof(LoggingFilter))]
public static class FilteredEndpoints
{
[HttpGet]
public static IResult GetValue() => TypedResults.Ok();
}
多个 filter 按声明顺序生成。为兼容 1.x,MiniControllerAttribute.FilterType 仍可用。
OpenAPI metadata
ProducesResponseType、ApiExplorerSettings.IgnoreApi 和 ApiExplorerSettings.GroupName 会映射为 Minimal API metadata:
[MiniController("/api/reports")]
[ApiExplorerSettings(GroupName = "Admin")]
public static class ReportEndpoints
{
[HttpGet]
[ProducesResponseType(typeof(Report), StatusCodes.Status200OK)]
public static IResult GetReport() => TypedResults.Ok(new Report(1));
}
.NET 10 第一方 OpenAPI 配置:
builder.Services.AddOpenApi();
builder.Services.AddOpenApi("Admin");
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
GroupName 使用 .WithGroupName(...),与 endpoint tag 是不同概念。
Native AOT
当 SDK 启用 AOT analyzer(例如项目设置 <PublishAot>true</PublishAot>)时,Generator 自动改用显式 RequestDelegate,不会调用带 Delegate 参数且要求反射/动态代码的 route overload。
AOT backend 支持:
- controller 与方法参数的 DI
HttpContext、HttpRequest、HttpResponse、ClaimsPrincipal、CancellationToken- route、query、header 的 binding name 与默认值
string、enum、StringValues和实现IParsable<T>的标量- 单个 JSON body
void、IResult、string、JSON DTO,以及对应的Task/ValueTask返回- authorization、OpenAPI metadata 和 endpoint filters
所有 JSON body、直接 JSON 返回和 typed ProducesResponseType 根类型都必须出现在应用的 JsonSerializerContext 中,并将该 context 注册到 HTTP JSON options:
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
[JsonSerializable(typeof(CreateItemRequest))]
[JsonSerializable(typeof(ItemResponse))]
internal partial class AppJsonSerializerContext : JsonSerializerContext;
缺少 required route/query/header 值或解析失败返回 400;缺少 required body、null body 或 JSON 无效返回 400;非 JSON body 返回 415。
form、[AsParameters]、keyed services、ref 参数、generic endpoint method 和其他无法静态生成的 binder 当前会报告 MC003,而不是退回反射。JSON 根类型未登记会报告 MC004。项目不需要也不应 suppress IL2026/IL3050。
生成内容
Generator 为每个 controller 生成独立扩展,并生成聚合入口:
Map{ControllerName}(IEndpointRouteBuilder)MapMiniController(IEndpointRouteBuilder)AddMiniControllers(IServiceCollection),仅在存在实例 controller 时生成
全局命名空间、嵌套类型、不同命名空间中的同名类型和 C# 关键字标识符均使用稳定的完整类型身份生成。
诊断
| ID | Severity | 含义 |
|---|---|---|
MC001 |
Error | 两个具体 endpoint 具有相同 HTTP method 与规范化 route |
MC002 |
Error | controller 类型形状无法安全注册,例如开放泛型 |
MC003 |
Error | Native AOT backend 不支持 endpoint 的 binding 或返回形状 |
MC004 |
Error | Native AOT 使用的 JSON 根类型未声明在 JsonSerializerContext 中 |
从 1.x 升级
2.0.0 包含有意的行为修正:
- 重复路由由运行时歧义改为编译错误。
[action]中的显式 method template 替换 token,不再重复追加。- 抽象 controller 不注册,但其 endpoint 可被具体派生类继承。
- 默认 controller 名使用 kebab-case,并优先移除最长后缀。
ApiExplorerSettings.GroupName不再错误映射成 tag。- 多重授权、binding alias、nullable/default values 和 typed
Produces不再丢失。
仓库验证
dotnet test MiniController.sln -c Release -warnaserror
dotnet pack MiniController.Attributes/MiniController.Attributes.csproj \
-c Release -o artifacts/packages -warnaserror
dotnet pack MiniController/MiniController.csproj \
-c Release -o artifacts/packages -warnaserror
NUGET_PACKAGES="$PWD/artifacts/package-consumer-cache" \
dotnet test tests/MiniController.PackageConsumer/Tests/MiniController.PackageConsumer.Tests.csproj \
-c Release -warnaserror
SampleApi 展示 .NET 10 第一方 OpenAPI;SampleAotApi 覆盖 DI、route/query/header、默认值、JSON body/response、异步返回、filter 和 native HTTP smoke。
增量 generator 基准独立运行,不属于功能测试门禁:
dotnet run -c Release \
--project benchmarks/MiniController.Generator.Benchmarks \
-- --filter '*IncrementalGeneratorBenchmarks*'
License
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.