.NET Academy

ASP.NET Core cheat sheet

Program.cs, minimal APIs, DI lifetimes, middleware order, config and auth.

Program.cs skeleton

var builder = WebApplication.CreateBuilder(args);
 
// ---- services ----
builder.Services.AddDbContext<ShopDb>(o => o.UseNpgsql(builder.Configuration.GetConnectionString("Shop")));
builder.Services.AddScoped<OrderService>();
builder.Services.AddOptions<SmtpOptions>().BindConfiguration("Smtp").ValidateOnStart();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddHealthChecks();
builder.Services.AddRateLimiter(o => o.AddFixedWindowLimiter("api", w => { w.PermitLimit = 100; w.Window = TimeSpan.FromMinutes(1); }));
 
var app = builder.Build();
 
// ---- pipeline (order matters) ----
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("frontend");
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
 
// ---- endpoints ----
app.MapOpenApi();
app.MapHealthChecks("/health");
app.MapOrderEndpoints();
 
app.Run();

Minimal API endpoints

var orders = app.MapGroup("/api/orders").RequireAuthorization().WithTags("Orders");
 
orders.MapGet("/", async (int? page, ShopDb db, CancellationToken ct) => await db.Orders.Take(20).ToListAsync(ct));
orders.MapGet("/{id:int}", async Task<Results<Ok<OrderDto>, NotFound>> (int id, ShopDb db) =>
    await db.Orders.FindAsync(id) is { } o ? TypedResults.Ok(o.ToDto()) : TypedResults.NotFound());
orders.MapPost("/", async (CreateOrder req, OrderService svc) =>
{
    var id = await svc.PlaceAsync(req);
    return TypedResults.Created($"/api/orders/{id}", new { id });
});
orders.MapDelete("/{id:int}", DeleteOrder).RequireAuthorization("AdminOnly");

Binding: route {id} β†’ parameter, simple types β†’ query string, complex types β†’ JSON body, registered types β†’ DI, ClaimsPrincipal, HttpContext, CancellationToken.

DI lifetimes

RegisterLifetimeUse for
AddSingleton<T>()One per appStateless/thread-safe services, caches
AddScoped<T>()One per requestDbContext, unit of work
AddTransient<T>()New each timeLightweight stateless helpers

Never inject a scoped service into a singleton.

Configuration

string host = builder.Configuration["Smtp:Host"]!;           // appsettings.json β†’ env vars (Smtp__Host)
public class OrderService(IOptions<SmtpOptions> smtp, ILogger<OrderService> log) { ... }

Precedence: appsettings.json < appsettings.{Environment}.json < user secrets (dev) < environment variables < command line.

Middleware

app.Use(async (ctx, next) =>
{
    // before
    await next(ctx);
    // after
});
app.UseMiddleware<RequestTimingMiddleware>();

Auth

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o => { o.Authority = "https://login.example.com"; o.Audience = "shop-api"; });
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", p => p.RequireRole("Admin"));
 
app.MapGet("/me", (ClaimsPrincipal user) => user.FindFirstValue(ClaimTypes.NameIdentifier)).RequireAuthorization();

401 = not authenticated, 403 = not allowed.

Logging

logger.LogInformation("Order {OrderId} placed by {UserId}", order.Id, userId);   // structured
logger.LogError(ex, "Payment failed for {OrderId}", order.Id);

Testing

public class Api(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
    [Fact] public async Task Health_IsOk() =>
        Assert.Equal(HttpStatusCode.OK, (await factory.CreateClient().GetAsync("/health")).StatusCode);
}
// Program.cs: public partial class Program;