C# Cheatsheet

Web, Data, and Testing

Use this C# reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Minimal API

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/health", () => Results.Ok(new { ok = true }));
app.MapGet("/users/{id:int}", (int id) => Results.Ok(new { id }));
app.MapPost("/users", (CreateUserRequest req) =>
    Results.Created($"/users/{req.Email}", req));

app.Run();

public record CreateUserRequest(string Email, string Name);

ASP.NET Core Minimal APIs bind route values, query strings, and JSON bodies by parameter type. Controllers are still common for larger MVC-style apps.

Dependency Injection

builder.Services.AddSingleton<IClock, SystemClock>();      // one instance forever
builder.Services.AddScoped<IUserRepository, PgUserRepo>(); // one per request
builder.Services.AddTransient<IEmailSender, SmtpSender>(); // new every resolve

app.MapGet("/users/{id:int}", async (int id, IUserRepository users) =>
    await users.FindAsync(id) is { } user ? Results.Ok(user) : Results.NotFound());

The built-in container creates services and injects them into endpoints, controllers, hosted services, and constructors. Scoped services must not be captured by singletons.

Configuration and Logging

string? cs = builder.Configuration.GetConnectionString("Default");
int limit = builder.Configuration.GetValue<int>("RateLimit:PerMinute");

app.MapGet("/work", (ILogger<Program> logger) =>
{
    logger.LogInformation("Handling work for {UserId}", 42);  // structured logging
    return Results.Ok();
});

Configuration merges appsettings files, environment variables, user secrets, and command-line values. Never hardcode secrets. Prefer message templates ({UserId}) over string interpolation in log calls.

HttpClient

builder.Services.AddHttpClient("api", client => {
    client.BaseAddress = new Uri("https://api.example.com/");
    client.Timeout = TimeSpan.FromSeconds(5);
});

// In a service (using System.Net.Http.Json)
var http = httpClientFactory.CreateClient("api");
User? user = await http.GetFromJsonAsync<User>("users/1");

using var res = await http.PostAsJsonAsync("users", new { email = "a@b.co" });
res.EnsureSuccessStatusCode();
string body = await res.Content.ReadAsStringAsync();

Use IHttpClientFactory instead of creating many raw new HttpClient() instances in long-running apps (socket exhaustion).

System.Text.Json

using System.Text.Json;
using System.Text.Json.Serialization;

public record User(
    [property: JsonPropertyName("user_name")] string Name,
    int Age);

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

string json = JsonSerializer.Serialize(new User("Ada", 36), options);
User? back = JsonSerializer.Deserialize<User>(json, options);
// Reading unknown-shape JSON without a model
using System.Text.Json.Nodes;

JsonNode? node = JsonNode.Parse("""{"user_name":"Ada","tags":["math"]}""");
string? name = (string?)node?["user_name"];
string? firstTag = (string?)node?["tags"]?[0];

System.Text.Json is the built-in serializer used by Minimal APIs and HttpClient JSON extensions. Deserialization is case-insensitive on the web defaults, case-sensitive with plain JsonSerializer unless PropertyNameCaseInsensitive = true.

Entity Framework Core

public class AppDb : DbContext
{
    public AppDb(DbContextOptions<AppDb> options) : base(options) { }
    public DbSet<User> Users => Set<User>();
}

builder.Services.AddDbContext<AppDb>(o => o.UseNpgsql(cs));

// Query
var user = await db.Users.SingleOrDefaultAsync(u => u.Email == email);
var page = await db.Users
    .Where(u => u.Active)
    .OrderBy(u => u.Name)
    .Skip(20).Take(10)
    .AsNoTracking()          // read-only, faster
    .ToListAsync();

// Insert, update, delete: one SaveChanges commits the unit of work
db.Users.Add(new User { Email = email });
user!.Name = "Ada";          // tracked entity, no explicit Update call needed
db.Users.Remove(user);
await db.SaveChangesAsync();
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database update

EF Core maps C# models to database tables and translates LINQ to SQL. Keep queries explicit, understand lazy/eager loading (Include), and inspect generated SQL for hot paths.

xUnit Test Shape

public class CalculatorTests
{
    [Fact]
    public void Add_ReturnsSum()
    {
        Assert.Equal(5, Calculator.Add(2, 3));
    }

    [Theory]
    [InlineData(2, 3, 5)]
    [InlineData(-1, 1, 0)]
    [InlineData(0, 0, 0)]
    public void Add_HandlesManyCases(int a, int b, int expected)
    {
        Assert.Equal(expected, Calculator.Add(a, b));
    }

    [Fact]
    public void Divide_ByZero_Throws()
    {
        var ex = Assert.Throws<ArgumentException>(() => Calculator.Divide(1, 0));
        Assert.Contains("zero", ex.Message);
    }
}

[Fact] is a single case; [Theory] + [InlineData] runs one test body over many inputs. Use unit tests for pure logic and integration tests for database, HTTP, serialization, and dependency wiring.

Common Test Commands

dotnet test
dotnet test --filter FullyQualifiedName~Calculator
dotnet test /p:CollectCoverage=true

Keep tests deterministic. Avoid real network calls in unit tests; mock interfaces or use local test servers.

Publish and Runtime

dotnet publish -c Release -o out
ASPNETCORE_URLS=http://0.0.0.0:8080 dotnet out/App.dll

Build release artifacts for deployment. Production apps should set environment, logging, health checks, and graceful shutdown behavior.