Skip to main content
US Army Corps of EngineersInstitute for Water Resources, Risk Management Center
Draft

This web application architecture guidance is still in draft and is subject to change.

ASP.NET Quick Reference

This page provides quick-reference tables and code snippets for common ASP.NET Core patterns used in RMC web applications.


Common Attributes

AttributePurposeExample
[ApiController]Marks class as API controller[ApiController] class MyController
[Route("path")]Sets base URL path[Route("api/analyses")]
[HttpGet], [HttpPost], etc.HTTP method for action[HttpPost("calculate")]
[Authorize]Requires authentication[Authorize] class or method
[AllowAnonymous]Allows unauthenticated access[AllowAnonymous] public Login()
[FromBody]Bind from JSON bodyCreate([FromBody] Dto dto)
[FromQuery]Bind from query stringList([FromQuery] int page)
[FromRoute]Bind from URL path[HttpGet("{id}")] Get(Guid id)
[Required]Validation: field required[Required] public string Name
[MaxLength(n)]Validation: max string length[MaxLength(255)] public string

Response Methods

HTTP StatusASP.NET MethodWhen to Use
200 OKreturn Ok(data)Successful GET or calculation
201 Createdreturn Created(url, data)Successful POST (item created)
204 No Contentreturn NoContent()Successful DELETE
400 Bad Requestreturn BadRequest(error)Validation failed
401 Unauthorizedreturn Unauthorized()No/invalid authentication token
403 Forbiddenreturn Forbid()User lacks permission
404 Not Foundreturn NotFound()Resource does not exist
409 Conflictreturn Conflict(error)Resource state conflict
500 Server Errorreturn StatusCode(500, error)Unhandled server error

Database Operations (Entity Framework)

OperationCodeNotes
Get by IDawait _db.Analyses.FindAsync(id)Returns null if not found
Get allawait _db.Analyses.ToListAsync()Returns empty list if none
Filter by field_db.Analyses.Where(a => a.UserId == uid)Returns IQueryable (not executed yet)
Filter (complex)_db.Analyses.Where(a => a.X > 5)Chain with .ToListAsync()
First match_db.Analyses.FirstOrDefaultAsync(a => ...)Returns null if not found
Countawait _db.Analyses.CountAsync()Executes query
Create_db.Analyses.Add(analysis)Entity tracked, not yet saved
Updateanalysis.Name = "New"Entity must be tracked
Delete_db.Analyses.Remove(analysis)Entity tracked for deletion
Save changesawait _db.SaveChangesAsync()Commits all pending changes

Controller Template

Basic CRUD Controller

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class AnalysesController : ControllerBase
{
private readonly AppDbContext _db;

public AnalysesController(AppDbContext db) => _db = db;

// GET /api/analyses
[HttpGet]
public async Task<IActionResult> GetAll()
{
var userId = GetCurrentUserId();
var items = await _db.Analyses
.Where(a => a.UserId == userId)
.ToListAsync();
return Ok(items);
}

// GET /api/analyses/{id}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id)
{
var item = await _db.Analyses.FindAsync(id);
if (item == null) return NotFound();
if (item.UserId != GetCurrentUserId()) return Forbid();
return Ok(item);
}

// POST /api/analyses
[HttpPost]
public async Task<IActionResult> Create([FromBody] AnalysisDto dto)
{
var analysis = new Analysis
{
Name = dto.Name,
UserId = GetCurrentUserId()
};
_db.Analyses.Add(analysis);
await _db.SaveChangesAsync();
return Created($"/api/analyses/{analysis.Id}", analysis);
}

// PUT /api/analyses/{id}
[HttpPut("{id}")]
public async Task<IActionResult> Update(Guid id, [FromBody] AnalysisDto dto)
{
var analysis = await _db.Analyses.FindAsync(id);
if (analysis == null) return NotFound();
if (analysis.UserId != GetCurrentUserId()) return Forbid();

analysis.Name = dto.Name;
analysis.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();

return Ok(analysis);
}

// DELETE /api/analyses/{id}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid id)
{
var analysis = await _db.Analyses.FindAsync(id);
if (analysis == null) return NotFound();
if (analysis.UserId != GetCurrentUserId()) return Forbid();

_db.Analyses.Remove(analysis);
await _db.SaveChangesAsync();

return NoContent();
}

private string GetCurrentUserId() =>
User.FindFirstValue(ClaimTypes.NameIdentifier)!;
}

Calculation Endpoint

[HttpPost("calculate/{analysisId}")]
public async Task<IActionResult> Calculate(Guid analysisId)
{
// 1. Get current user
var userId = GetCurrentUserId();

// 2. Load analysis
var analysis = await _db.Analyses.FindAsync(analysisId);
if (analysis == null) return NotFound();

// 3. Check authorization
if (analysis.UserId != userId) return Forbid();

// 4. Run calculation (via HTTP client or service)
var result = await _calculationService.RunAsync(analysisId, userId);

// 5. Save results
analysis.ResultData = JsonSerializer.SerializeToDocument(result.Data, JsonDefaults.CamelCase);
analysis.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();

// 6. Return messaging contract response
return Ok(new ApiResponse<object>
{
Status = "success",
Data = result.Data,
Warnings = result.Warnings,
CanRun = true
});
}

Entity Template

Domain entities are POCOs (Plain Old CLR Objects) with no data annotations. Database mapping is configured separately via Fluent API (see Backend Architecture — Entity Configuration).

// Domain/Entities/Analysis.cs
public class Analysis
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;

// JSONB columns for flexible data
public JsonDocument? Parameters { get; set; }
public JsonDocument? ResultData { get; set; }

// Navigation property
public User User { get; set; } = null!;
}
// Infrastructure/Data/Configurations/AnalysisConfiguration.cs
public class AnalysisConfiguration : IEntityTypeConfiguration<Analysis>
{
public void Configure(EntityTypeBuilder<Analysis> builder)
{
builder.ToTable("analyses");
builder.HasKey(a => a.Id);
builder.Property(a => a.Id).HasDefaultValueSql("gen_random_uuid()");
builder.Property(a => a.Name).IsRequired().HasMaxLength(255);
builder.Property(a => a.UserId).IsRequired();
builder.Property(a => a.Parameters).HasColumnType("jsonb");
builder.Property(a => a.ResultData).HasColumnType("jsonb");
builder.Property(a => a.CreatedAt).HasDefaultValueSql("NOW()");
builder.HasIndex(a => a.UserId);
builder.HasOne<User>().WithMany().HasForeignKey(a => a.UserId);
}
}

Logging Patterns

public class AnalysisController : ControllerBase
{
private readonly ILogger<AnalysisController> _logger;

public AnalysisController(ILogger<AnalysisController> logger)
{
_logger = logger;
}

[HttpPost("calculate/{id}")]
public async Task<IActionResult> Calculate(Guid id)
{
// Use structured logging with named parameters
_logger.LogInformation(
"[API] POST /api/analysis/calculate/{AnalysisId}",
id);

try
{
_logger.LogDebug(
"[CALC] Starting calculation: Id={Id}, Iterations={Iterations}",
id, config.Iterations);

// ... calculation ...

_logger.LogInformation(
"[CALC] Completed in {ElapsedMs}ms",
stopwatch.ElapsedMilliseconds);

return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex,
"[ERROR] Calculation failed for analysis {Id}",
id);
throw;
}
}
}

Common Gotchas

Forgetting await

// WRONG: Returns Task instead of data
public async Task<IActionResult> Get()
{
var items = _db.Analyses.ToListAsync(); // Missing await!
return Ok(items);
}

// CORRECT
public async Task<IActionResult> Get()
{
var items = await _db.Analyses.ToListAsync();
return Ok(items);
}

Forgetting null checks

// WRONG: NullReferenceException if not found
public async Task<IActionResult> Get(Guid id)
{
var item = await _db.Analyses.FindAsync(id);
return Ok(item.Name); // Crashes if item is null!
}

// CORRECT
public async Task<IActionResult> Get(Guid id)
{
var item = await _db.Analyses.FindAsync(id);
if (item == null) return NotFound();
return Ok(item.Name);
}

Forgetting to save

// WRONG: Changes not persisted
public async Task<IActionResult> Update(Guid id, UpdateDto dto)
{
var item = await _db.Analyses.FindAsync(id);
item.Name = dto.Name;
// Missing SaveChangesAsync!
return Ok(item);
}

// CORRECT
public async Task<IActionResult> Update(Guid id, UpdateDto dto)
{
var item = await _db.Analyses.FindAsync(id);
item.Name = dto.Name;
await _db.SaveChangesAsync();
return Ok(item);
}

Forgetting authorization

// WRONG: Any user can access any analysis
public async Task<IActionResult> Get(Guid id)
{
var item = await _db.Analyses.FindAsync(id);
return Ok(item);
}

// CORRECT: Check ownership
public async Task<IActionResult> Get(Guid id)
{
var item = await _db.Analyses.FindAsync(id);
if (item == null) return NotFound();
if (item.UserId != GetCurrentUserId()) return Forbid();
return Ok(item);
}

File Locations

WhatWhereLayer
API Controllersbackend/src/WebApp.API/Controllers/API
Middlewarebackend/src/WebApp.API/Middleware/API
App Entry Pointbackend/src/WebApp.API/Program.csAPI
App Configurationbackend/src/WebApp.API/appsettings.jsonAPI
Servicesbackend/src/WebApp.Application/Services/Application
Interfacesbackend/src/WebApp.Application/Interfaces/Application
DTOs (incl. ApiResponse)backend/src/WebApp.Application/DTOs/Application
Domain Entitiesbackend/src/WebApp.Domain/Entities/Domain
Domain Enumsbackend/src/WebApp.Domain/Enums/Domain
DbContextbackend/src/WebApp.Infrastructure/Data/AppDbContext.csInfrastructure
Entity Configurationsbackend/src/WebApp.Infrastructure/Data/Configurations/Infrastructure
Repositoriesbackend/src/WebApp.Infrastructure/Repositories/Infrastructure
Typed HTTP Clientsbackend/src/WebApp.Infrastructure/Clients/Infrastructure