coder-csharp-aspnetcore-api

<skill_overview> Build robust, performant, and well-documented ASP.NET Core APIs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "coder-csharp-aspnetcore-api" with this command: npx skills add ozerohax/assistagents/ozerohax-assistagents-coder-csharp-aspnetcore-api

<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&lt;ActionResult&lt;UserDto&gt;&gt; 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&lt;ActionResult&lt;UserDto&gt;&gt; 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 =&gt; policy.RequireClaim("Permission", "Orders.Edit"));

options.AddPolicy("MinAge18", policy =&gt; 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 =&gt; u.ToDto()) .ToListAsync();

return Ok(new PagedResult&lt;UserDto&gt;(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 =&gt; { 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() =&gt; Ok("v2");

}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

coder-rust-async-concurrency

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

coder-csharp-efcore-queries

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

coder-csharp-error-handling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

coder-rust-axum-api

No summary provided by upstream source.

Repository SourceNeeds Review