.NET: ASP.NET Core Web APIs
Minimal API (modern approach)
// Program.cs — entry point and composition root
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
app.MapOpenApi();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
// Map routes
app.MapGet("/users", async (IUserService svc) =>
Results.Ok(await svc.GetAllAsync()))
.WithName("GetUsers")
.WithTags("Users");
app.MapGet("/users/{id:int}", async (int id, IUserService svc) => {
var user = await svc.GetByIdAsync(id);
return user is null ? Results.NotFound() : Results.Ok(user);
});
app.MapPost("/users", async (CreateUserDto dto, IUserService svc) => {
var user = await svc.CreateAsync(dto);
return Results.CreatedAtRoute("GetUser", new { id = user.Id }, user);
});
app.MapPut("/users/{id:int}", async (int id, UpdateUserDto dto, IUserService svc) => {
var updated = await svc.UpdateAsync(id, dto);
return updated ? Results.NoContent() : Results.NotFound();
});
app.MapDelete("/users/{id:int}", async (int id, IUserService svc) => {
await svc.DeleteAsync(id);
return Results.NoContent();
});
app.Run();Controller-Based API
// Controllers/UsersController.cs
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly IUserService _service;
public UsersController(IUserService service)
{
_service = service;
}
[HttpGet]
[ProducesResponseType<List<UserDto>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 25)
{
var users = await _service.GetPagedAsync(page, pageSize);
return Ok(users);
}
[HttpGet("{id:int}")]
[ProducesResponseType<UserDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
var user = await _service.GetByIdAsync(id);
return user is null ? NotFound() : Ok(user);
}
[HttpPost]
[ProducesResponseType<UserDto>(StatusCodes.Status201Created)]
[ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Create([FromBody] CreateUserDto dto)
{
var user = await _service.CreateAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
[HttpPatch("{id:int}")]
public async Task<IActionResult> Update(int id, [FromBody] UpdateUserDto dto)
{
return await _service.UpdateAsync(id, dto) ? NoContent() : NotFound();
}
}DTOs & Validation
// Data Transfer Objects — separate from domain models
public record UserDto(int Id, string Name, string Email, DateTime CreatedAt);
public record CreateUserDto(
[Required, MaxLength(100)] string Name,
[Required, EmailAddress] string Email,
[Required, MinLength(8)] string Password
);
public record UpdateUserDto(
[MaxLength(100)] string? Name,
[EmailAddress] string? Email
);
// Fluent validation (alternative to data annotations)
// Install: dotnet add package FluentValidation.AspNetCore
public class CreateUserValidator : AbstractValidator<CreateUserDto>
{
public CreateUserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
RuleFor(x => x.Password).MinimumLength(8)
.Matches("[A-Z]").WithMessage("Password must contain an uppercase letter");
}
}Authentication (JWT)
// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts => {
opts.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!)),
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateLifetime = true,
};
});
builder.Services.AddAuthorization(opts => {
opts.AddPolicy("AdminOnly", p => p.RequireRole("admin"));
});
// Protect endpoints
[Authorize] // any authenticated user
[Authorize(Policy = "AdminOnly")] // admin only
[Authorize(Roles = "admin,moderator")] // role-based
[AllowAnonymous] // override — no authKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free