📄

Aspnetcore Cheatsheet

Intermediate 2 min read 300 words

ASP.NET Core Quick Reference Cheatsheet

Request Pipeline

HTTP Request → Middleware 1 → Middleware 2 → ... → Routing → Endpoint
HTTP Response ← Middleware 1 ← Middleware 2 ← ... ← Routing ← Endpoint

Middleware Order (Critical!)

app.UseExceptionHandler("/Error");  // 1. Exception handling (first)
app.UseHsts();                       // 2. HSTS
app.UseHttpsRedirection();           // 3. HTTPS redirect
app.UseStaticFiles();                // 4. Static files (short-circuit)
app.UseRouting();                    // 5. Routing
app.UseCors();                       // 6. CORS
app.UseAuthentication();             // 7. Authentication
app.UseAuthorization();              // 8. Authorization
app.UseEndpoints(...);               // 9. Endpoints (last)

Custom Middleware

app.Use(async (context, next) =>
{
    // Before next middleware
    await next();
    // After next middleware
});

app.Run(async context =>  // Terminal middleware
{
    await context.Response.WriteAsync("Hello");
});

Dependency Injection

Service Lifetimes

Lifetime Description Use For
Singleton One instance for app lifetime Stateless services, caches
Scoped One instance per request DbContext, repositories
Transient New instance every time Lightweight, stateless

Registration Patterns

// Basic
services.AddSingleton<IService, Service>();
services.AddScoped<IService, Service>();
services.AddTransient<IService, Service>();

// Factory
services.AddScoped<IService>(sp => new Service(sp.GetRequiredService<IDep>()));

// Multiple implementations
services.AddScoped<IService, ServiceA>();
services.AddScoped<IService, ServiceB>();
// IEnumerable<IService> injects both

Scoped Service in Singleton (Safe Pattern)

public class MySingleton
{
    private readonly IServiceScopeFactory _scopeFactory;

    public async Task DoWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService<IScopedService>();
        await scoped.ProcessAsync();
    }
}

Configuration

Precedence Order (Last Wins)

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. User Secrets (Development)
  4. Environment Variables
  5. Command Line Arguments

Options Pattern

// Registration
services.Configure<MyOptions>(config.GetSection("MyOptions"));

// Usage comparison
IOptions<T>         // Singleton, read once at startup
IOptionsSnapshot<T> // Scoped, re-read per request
IOptionsMonitor<T>  // Singleton, supports change notifications

// Validation
services.AddOptions<MyOptions>()
    .Bind(config.GetSection("MyOptions"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Authentication & Authorization

JWT Setup (Quick)

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "your-issuer",
            ValidAudience = "your-audience",
            IssuerSigningKey = new SymmetricSecurityKey(key)
        };
    });

Policy-Based Authorization

// Define
services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
    options.AddPolicy("Premium", policy => policy.RequireClaim("Subscription", "Premium"));
});

// Use
[Authorize(Policy = "AdminOnly")]
public IActionResult AdminPage() { }

Custom Authorization Handler

public class MinAgeRequirement : IAuthorizationRequirement
{
    public int MinAge { get; }
    public MinAgeRequirement(int age) => MinAge = age;
}

public class MinAgeHandler : AuthorizationHandler<MinAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinAgeRequirement requirement)
    {
        var birthDate = context.User.FindFirst("BirthDate")?.Value;
        if (birthDate != null && CalculateAge(birthDate) >= requirement.MinAge)
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

Routing

Attribute Routing

[Route("api/[controller]")]  // api/products
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet]                           // GET api/products
    [HttpGet("{id:int}")]               // GET api/products/5
    [HttpGet("search/{name:alpha}")]    // GET api/products/search/phone
    [HttpPost]                          // POST api/products
    [HttpPut("{id}")]                   // PUT api/products/5
    [HttpDelete("{id}")]                // DELETE api/products/5
}

Route Constraints

Constraint Example Matches
int {id:int} 123
guid {id:guid} CD2C1638-…
alpha {name:alpha} abc
min(n) {age:min(18)} 18+
regex(expr) {ssn:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)} 123-45-6789

Minimal APIs vs Controllers

Minimal APIs

app.MapGet("/items", async (IItemService service) =>
    Results.Ok(await service.GetAllAsync()));

app.MapPost("/items", async (Item item, IItemService service) =>
{
    await service.CreateAsync(item);
    return Results.Created($"/items/{item.Id}", item);
});

Quick Comparison

Feature Minimal APIs Controllers
Performance Slightly faster Standard
Model binding Automatic Automatic
Validation Manual/Filter Built-in
Filters Yes (.NET 7+) Full support
Best for Simple APIs Complex APIs

Caching

Response Caching vs Output Caching

// Response Caching (HTTP headers, client/proxy caching)
[ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "id" })]
public IActionResult Get() { }

// Output Caching (.NET 7+, server-side)
app.MapGet("/data", [OutputCache(Duration = 60)] async () => GetData());

Cache Invalidation

// Tag-based invalidation
app.MapGet("/products", [OutputCache(Tags = new[] { "products" })] () => GetProducts());
app.MapPost("/products", async (IOutputCacheStore cache) =>
{
    await cache.EvictByTagAsync("products", default);
    return Results.Ok();
});

Background Services

Basic BackgroundService

public class WorkerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await DoWorkAsync(stoppingToken);
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        // Graceful shutdown logic
        await base.StopAsync(cancellationToken);
    }
}

Common Interview Answers

Q: Middleware vs Filters?

  • Middleware: Request pipeline level, all requests
  • Filters: MVC/API level, specific to controllers/actions

Q: IOptions vs IOptionsSnapshot vs IOptionsMonitor?

  • IOptions<T>: Singleton, never changes
  • IOptionsSnapshot<T>: Scoped, updates per request
  • IOptionsMonitor<T>: Singleton, supports OnChange

Q: How to handle scoped service in singleton?

  • Use IServiceScopeFactory to create scope, resolve within using block

Q: JWT refresh token strategy?

  • Short-lived access token + long-lived refresh token
  • Refresh token rotation (invalidate old on use)
  • Store refresh tokens in database for revocation

Q: Output caching vs Response caching?

  • Output: Server-side, more control, .NET 7+
  • Response: HTTP headers, client/proxy caching

Quick Debugging Tips

// See generated SQL (EF Core)
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connString)
           .EnableSensitiveDataLogging()
           .LogTo(Console.WriteLine, LogLevel.Information));

// Request logging
app.UseSerilogRequestLogging();

// Check DI registrations
foreach (var service in services)
    Console.WriteLine($"{service.ServiceType.Name} -> {service.ImplementationType?.Name}");