| name | storm-api |
| description | Implement a C# backend using the Storm.Api framework. Use when writing business logic, logging, extension methods, or general Storm.Api patterns. |
| user-invocable | true |
| disable-model-invocation | false |
You are helping implement a C# backend using the Storm.Api framework. Follow all patterns and constraints below exactly — they are non-negotiable.
The user's request: $ARGUMENTS
Framework Overview
Storm.Api is an opinionated ASP.NET Core framework built on CQRS. Business logic lives in Action classes, never in controllers. Controllers are thin, source-generated glue. Three NuGet packages:
Storm.Api — core framework (net10.0)
Storm.Api.Dtos — response types, shared with clients (netstandard2.0)
Storm.Api.SourceGenerators — Roslyn generator for [WithAction<T>] (netstandard2.0)
For specific patterns, use the focused skills:
/storm-api-init — new project setup (NuGet packages, Program.cs, Startup.cs, code style)
/storm-api-endpoint — actions, controllers, exceptions, response DTOs, OpenAPI documentation attributes ([Summary], [Description], [ErrorCode], [HttpError], [MediaType], [SuccessCode], [InternalActionCall<>])
/storm-api-auth — IActionAuthenticator, JWT, API key, custom authenticators, refresh tokens
/storm-api-database — entities, OrmLite queries, repositories
/storm-api-database-migrations — writing and registering migrations
/storm-api-email — email sending with Resend, temporary email detection
/storm-api-workers — background tasks, periodic/scheduled hosted services, retry strategies
/storm-api-redis — Redis caching, key-value storage, pub/sub messaging
/storm-api-queues — in-memory queues, buffered/throttled batching, queue workers
Logging
Never use ILogger<T> or Microsoft.Extensions.Logging. Use ILogService exclusively.
var log = Resolve<ILogService>();
log.Log(LogLevel.Warning, x => x
.WriteProperty("message", "Something went wrong")
.WriteProperty("userId", userId)
.WriteException(ex));
log.Information(x => x.WriteProperty("event", "user_created").WriteProperty("id", id));
log.Warning(x => x.WriteProperty("message", "Rate limit hit"));
log.Error(x => x.WriteProperty("message", "Unexpected failure").WriteException(ex));
Register in Startup.ConfigureServices:
RegisterConsoleLogger(services, LogLevel.Debug);
RegisterSerilogLogger(services, "Serilog");
RegisterElasticSearchLogger(services, "ElasticSearch");
RegisterElasticSearchHostedLogger(services, "ElasticSearch");
Key Extension Methods
return myValue.AsTask();
return null.AsTaskNullable<int>();
str.IsNullOrEmpty()
str.IsNotNullOrEmpty()
str.ValueIfNullOrEmpty("fallback")
str.NullIfEmpty()
str.OrEmpty()
await services.ExecuteAction<MyAction, MyParam, MyOutput>(param);
await services.ExecuteWithScope(async sp => { ... });
var svc = Resolve<IMyService>();
var keyed = ResolveKeyed<IMyService>("key");
Anti-Patterns to Avoid
| ❌ Wrong | ✅ Correct |
|---|
Task.FromResult(value) | value.AsTask() |
| Constructor-inject services | Resolve<T>() inside methods |
ILogger<T> | ILogService |
| Entity Framework / Dapper | ServiceStack.OrmLite |
| Logic in controller | Logic in Action class |
DateTime.Now / DateTime.UtcNow / DateTimeOffset.UtcNow | Resolve<TimeProvider>().GetUtcNow().UtcDateTime (or inject TimeProvider via constructor in non-Resolve classes) |