一键导入
storm-api-endpoint
Implement a C# endpoint using the Storm.Api framework with CQRS actions, controllers, source generators, exceptions, and response DTOs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Implement a C# endpoint using the Storm.Api framework with CQRS actions, controllers, source generators, exceptions, and response DTOs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | storm-api-endpoint |
| description | Implement a C# endpoint using the Storm.Api framework with CQRS actions, controllers, source generators, exceptions, and response DTOs. |
| user-invocable | true |
| disable-model-invocation | false |
You are helping implement a C# endpoint using the Storm.Api framework. Follow all patterns below exactly. For global rules (logging, extensions, anti-patterns), see /storm-api.
The user's request: $ARGUMENTS
All business logic lives in Action classes. Never put logic in controllers.
public class MyQueryParameter
{
public required string Id { get; init; }
}
public class MyQuery(IServiceProvider services) : BaseAction<MyQueryParameter, MyDto>(services)
{
protected override async Task<MyDto> Action(MyQueryParameter parameter)
{
return await UseReadConnection(db => db.SingleByIdAsync<MyEntity>(parameter.Id));
}
}
Rules:
(IServiceProvider services) — never inject individual servicesBaseAction<TParameter, TOutput> — one abstract method to implement: Action(parameter)ValidateParameter() → PrepareParameter() → Action(parameter)ValidateParameter to return false to throw HTTP 400PrepareParameter to transform/enrich the parameter before Action is calledUnit as output when there is no response body: BaseAction<MyParam, Unit>, return Unit.Defaultpublic class SecureCommand(IServiceProvider services)
: BaseAuthenticatedAction<SecureCommandParameter, Unit, CurrentUser>(services)
{
protected override async Task Authorize(SecureCommandParameter parameter, CurrentUser account)
{
if (!account.IsAdmin)
throw new DomainHttpCodeException(HttpStatusCode.Forbidden);
}
protected override async Task<Unit> Action(SecureCommandParameter parameter, CurrentUser account)
{
return Unit.Default;
}
}
Rules:
BaseAuthenticatedAction<TParameter, TOutput, TAccount> — action receives the resolved accountValidateParameter() → PrepareParameter() → authenticate via IActionAuthenticator<TAccount> → Authorize() → Action(parameter, account)IActionAuthenticator<TAccount> in DI for each account type.UnauthorizedIfNull() on the authenticator result for a clean null checkControllers are partial classes. The [WithAction<T>] attribute causes the Roslyn generator to implement the partial method automatically.
public partial class UsersController(IServiceProvider services) : BaseController(services)
{
[HttpGet("{id}")]
[WithAction<GetUserQuery>]
[Tags("Users")]
public partial Task<ActionResult<Response<UserDto>>> GetUser([FromRoute] string id);
[HttpPost]
[WithAction<CreateUserCommand>]
[Tags("Users")]
public partial Task<ActionResult<Response>> CreateUser([FromBody] CreateUserRequest body);
[HttpGet("{id}/avatar")]
[WithAction<GetAvatarQuery>]
[Tags("Users")]
public partial Task<IActionResult> GetAvatar([FromRoute] string id);
}
Rules:
partial, extend BaseController, receive only IServiceProvider[WithAction<TAction>] — never implement the partial method manually[MapTo(nameof(MyParam.SomeProperty))] when the HTTP parameter name differs from the parameter class property[Tags("Group")] on the controller method to group endpoints in the OpenAPI documentTask<ActionResult<Response<T>>> — typed outputTask<ActionResult<Response>> — Unit output (no body)Task<IActionResult> — file download (ApiFileResult output)[ProducesResponseType], [EndpointSummary], or [EndpointDescription] on the controller — the generator emits these from the action class. Use the documentation attributes below instead.Document the endpoint contract on the action class — the generator emits [EndpointSummary], [EndpointDescription], [ProducesResponseType<T>], and [OpenApiErrorCodes(...)] on the controller method automatically (BaseStartup wires up AddStormOpenApi() which surfaces error codes as the x-error-codes extension).
All attributes are in Storm.Api.SourceGenerators.ActionMethods:
[Summary("...")] — endpoint summary (single)[Description("...")] — long description (multiple allowed, joined)[ErrorCode("CODE", Description = "...")] — business error from DomainException (multiple)[HttpError(HttpStatusCode.X, Description = "...")] — HTTP error from DomainHttpCodeException (multiple)[SuccessCode(code, Description = "...")] — override the default 200 (multiple)[MediaType("image/png")] — required on ApiFileResult actions to declare the file content type[InternalActionCall<TAction>] — pull [ErrorCode] / [HttpError] from a delegated action (multiple)Rules:
throw new DomainException("CODE", ...) with a matching [ErrorCode("CODE", ...)] on the action.throw new DomainHttpCodeException(HttpStatusCode.X, ...) with a matching [HttpError(HttpStatusCode.X, ...)].[InternalActionCall<OtherAction>] instead of restating its errors.See examples/documentation-attributes.md for a full sample, the attribute reference, and the exact generation behavior.
// Business error → HTTP 200, IsSuccess=false
throw new DomainException("ERROR_CODE", "Human readable message");
// HTTP error → specific status code
throw new DomainHttpCodeException(HttpStatusCode.NotFound, "NOT_FOUND", "Resource not found");
throw new DomainHttpCodeException(HttpStatusCode.Forbidden);
// Null-check shortcuts (prefer these over manual throws)
var entity = await GetEntity(id).NotFoundIfNull("NOT_FOUND", "Entity not found");
var user = await Authenticate().UnauthorizedIfNull();
var item = GetItem(id).BadRequestIfNull("MISSING", "Item required");
var data = Load(id).ForbiddenIfNull();
Rules:
DomainException → caller gets HTTP 200 with { "is_success": false, "error_code": "...", "error_message": "..." }DomainHttpCodeException → caller gets the specified HTTP status codeT? and Task<T?> — use them everywhere instead of if (x == null) throwAll endpoints return framework response wrappers. Never return raw objects.
Types (from Storm.Api.Dtos):
Response — no data ({ "is_success": true })Response<T> — with typed data payload ({ "is_success": true, "data": { ... } })PaginatedResponse<T> — for paginated lists (has Page, Count, TotalCount)ApiFileResult — for file downloads: ApiFileResult.Create(bytes, "application/pdf", "file.pdf")For a full paginated response example (action + controller), see examples/paginated-response.md.
| ❌ Wrong | ✅ Correct |
|---|---|
| Logic in controller | Logic in Action class |
| Implement partial method manually | Let [WithAction<T>] generate it |
if (x == null) throw new ... | .NotFoundIfNull() / .UnauthorizedIfNull() etc. |
if (x == false) throw new ... | .NotFoundIfFalse() / .UnauthorizedIfFalse() etc. |
[ProducesResponseType<Response>(400)] on controller | [HttpError(HttpStatusCode.BadRequest)] on action |
[ProducesResponseType<FileResult>(200, "image/png")] on controller | [MediaType("image/png")] on action |
[EndpointSummary("...")] / [EndpointDescription("...")] on controller | [Summary("...")] / [Description("...")] on action |
Restating an inner action's errors with [ErrorCode] / [HttpError] | [InternalActionCall<InnerAction>] |
Implement authentication using the Storm.Api framework with JWT, API key, custom authenticators, and refresh tokens. Use when adding auth to endpoints.
Write a database migration using the Storm.Api framework with BaseMigration and OrmLite. Use when creating or modifying database tables, columns, or seed data.
Implement database entities and repositories using the Storm.Api framework with ServiceStack.OrmLite. Use when creating entities, repositories, or database queries.
Implement email sending using the Storm.Api framework with Resend provider and temporary email detection. Use when sending emails or validating email addresses.
Initialize a new Storm.Api project with NuGet packages, Program.cs, Startup.cs, and code style configuration. Use when setting up a new project from scratch.
Implement in-memory queues using the Storm.Api framework with ItemQueue, BufferedItemQueue, ThrottledBufferedItemQueue, and queue workers.