MiniController.Attributes
2.0.0
dotnet add package MiniController.Attributes --version 2.0.0
NuGet\Install-Package MiniController.Attributes -Version 2.0.0
<PackageReference Include="MiniController.Attributes" Version="2.0.0" />
<PackageVersion Include="MiniController.Attributes" Version="2.0.0" />
<PackageReference Include="MiniController.Attributes" />
paket add MiniController.Attributes --version 2.0.0
#r "nuget: MiniController.Attributes, 2.0.0"
#:package MiniController.Attributes@2.0.0
#addin nuget:?package=MiniController.Attributes&version=2.0.0
#tool nuget:?package=MiniController.Attributes&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
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- 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.