coder-csharp-aspnetcore-api
npx skills add https://github.com/ozerohax/assistagents --skill coder-csharp-aspnetcore-api
Agent 安装分布
Skill 文档
<skill_overview> Build robust, performant, and well-documented ASP.NET Core APIs Creating new API endpoints Choosing Controllers vs Minimal APIs Setting up authentication/authorization Implementing caching or rate limiting Documenting APIs with OpenAPI Microsoft Web API Documentation </skill_overview> <controllers_vs_minimal> <use_controllers_when> Large applications with complex routing Need filters, model binding features Team prefers MVC structure Complex authorization with filters </use_controllers_when> <use_minimal_when> Microservices, small APIs Simple endpoints without much middleware Performance-critical scenarios Prefer less ceremony/boilerplate </use_minimal_when> <controller_example> [ApiController] [Route(“api/[controller]”)] public class UsersController : ControllerBase { private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> GetById(int id)
{
var user = await _userService.GetByIdAsync(id);
return user == null ? NotFound() : Ok(user);
}
[HttpPost]
[ProducesResponseType(typeof(UserDto), 201)]
[ProducesResponseType(400)]
public async Task<ActionResult<UserDto>> Create(CreateUserDto dto)
{
var user = await _userService.CreateAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
} </controller_example> <minimal_example> var app = builder.Build(); var users = app.MapGroup(“/api/users”); users.MapGet(“/{id}”, async (int id, IUserService service) => await service.GetByIdAsync(id) is UserDto user ? TypedResults.Ok(user) : TypedResults.NotFound()) .WithName(“GetUserById”) .Produces<UserDto>(200) .Produces(404); users.MapPost(“/”, async (CreateUserDto dto, IUserService service) => { var user = await service.CreateAsync(dto); return TypedResults.Created($”/api/users/{user.Id}”, user); }) .WithName(“CreateUser”) .Produces<UserDto>(201); </minimal_example> </controllers_vs_minimal> <dtos_and_mapping> Never expose entities directly – always use DTOs Security: prevent over-posting attacks Decoupling: API contract independent of database Control: expose only needed fields <mapping_options> Fastest, explicit, most control Source-generated, fast, compile-time safe Faster than AutoMapper, good DX Most features, slower </mapping_options> // Entity (never expose directly) public class User { public int Id { get; set; } public string Email { get; set; } public string PasswordHash { get; set; } // Never expose! public DateTime CreatedAt { get; set; } } // DTOs (expose these) public record UserDto(int Id, string Email, DateTime CreatedAt); public record CreateUserDto(string Email, string Password); // Manual mapping public static UserDto ToDto(this User user) => new(user.Id, user.Email, user.CreatedAt); </dtos_and_mapping> <jwt_setup> builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = “https://your-auth-server“; options.Audience = builder.Configuration[“Auth:Audience”]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true }; }); builder.Services.AddAuthorization(); app.UseAuthentication(); app.UseAuthorization(); </jwt_setup> <policy_authorization> // Define policies builder.Services.AddAuthorization(options => { options.AddPolicy(“AdminOnly”, policy => policy.RequireRole(“Admin”));
options.AddPolicy("CanEditOrders", policy =>
policy.RequireClaim("Permission", "Orders.Edit"));
options.AddPolicy("MinAge18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
}); // Apply to endpoints [Authorize(Policy = “AdminOnly”)] public IActionResult AdminAction() => Ok(); // Minimal API app.MapDelete(“/orders/{id}”, DeleteOrder) .RequireAuthorization(“CanEditOrders”); </policy_authorization> public record PagedResult<T>( IEnumerable<T> Items, int TotalCount, int Page, int PageSize) { public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); public bool HasPrevious => Page > 1; public bool HasNext => Page < TotalPages; } [HttpGet] public async Task<ActionResult<PagedResult<UserDto>>> GetUsers( [FromQuery] int page = 1, [FromQuery] int pageSize = 10) { var query = _context.Users .AsNoTracking() .OrderBy(u => u.CreatedAt);
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(u => u.ToDto())
.ToListAsync();
return Ok(new PagedResult<UserDto>(items, totalCount, page, pageSize));
} Always include OrderBy before Skip/Take <output_caching description=”.NET 7+”> builder.Services.AddOutputCache(options => { options.DefaultExpirationTimeSpan = TimeSpan.FromMinutes(5); }); app.UseOutputCache(); // Apply to endpoint app.MapGet(“/products”, GetProducts) .CacheOutput(p => p.Expire(TimeSpan.FromMinutes(10))); // With tag for invalidation app.MapGet(“/products/{id}”, GetProduct) .CacheOutput(p => p.Tag(“products”)); app.MapPost(“/products”, async (Product p, IOutputCacheStore cache) => { // Invalidate cache after create await cache.EvictByTagAsync(“products”, default); return Results.Created(); }); </output_caching> <response_caching> builder.Services.AddResponseCaching(); app.UseResponseCaching(); [HttpGet] [ResponseCache(Duration = 60, VaryByQueryKeys = new[] { “page” })] public IActionResult GetProducts() => Ok(); </response_caching> <rate_limiting description=”.NET 7+”> builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter(“fixed”, opt => { opt.PermitLimit = 100; opt.Window = TimeSpan.FromMinutes(1); opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; opt.QueueLimit = 10; });
options.AddSlidingWindowLimiter("sliding", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 4;
});
options.RejectionStatusCode = 429;
}); app.UseRateLimiter(); app.MapGet(“/api/resource”, GetResource) .RequireRateLimiting(“fixed”); </rate_limiting> <response_compression> builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add<BrotliCompressionProvider>(); options.Providers.Add<GzipCompressionProvider>(); }); builder.Services.Configure<BrotliCompressionProviderOptions>(options => options.Level = CompressionLevel.Fastest); app.UseResponseCompression(); // Before endpoints </response_compression> builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc(“v1”, new OpenApiInfo { Title = “My API”, Version = “v1”, Description = “API for managing resources” });
// Include XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// JWT auth in Swagger
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
}); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } <xml_comments> /// <summary> /// Gets a user by ID /// </summary> /// <param name=”id”>The user identifier</param> /// <returns>The user or 404 if not found</returns> /// <response code=”200″>Returns the user</response> /// <response code=”404″>User not found</response> [HttpGet(“{id}”)] [ProducesResponseType(typeof(UserDto), 200)] [ProducesResponseType(404)] public async Task<ActionResult<UserDto>> GetById(int id) </xml_comments> <csproj_setting> <PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup> </csproj_setting> builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; options.ApiVersionReader = ApiVersionReader.Combine( new UrlSegmentApiVersionReader(), new HeaderApiVersionReader(“X-Api-Version”)); }); // URL versioning [ApiController] [Route(“api/v{version:apiVersion}/[controller]”)] [ApiVersion(“1.0”)] [ApiVersion(“2.0”)] public class UsersController : ControllerBase { [HttpGet, MapToApiVersion(“1.0”)] public IActionResult GetV1() => Ok(“v1”);
[HttpGet, MapToApiVersion("2.0")]
public IActionResult GetV2() => Ok("v2");
}