Backend development: C#

Sefism.

Chapter 01

C# and ASP.NET Core essentials

The C# features you use in every ASP.NET Core service, and how the host, DI container and CLI fit together.

async/await and Task

Also called Task, CancellationToken, TAP

What
An async method returns a Task or Task<T>; await frees the thread while I/O is in flight. A CancellationToken lets the caller stop the work, for example when the client disconnects.
Why
ASP.NET Core serves many requests with a small thread pool only if I/O is awaited instead of blocked on.
C#
async Task<decimal> TotalAsync(int userId, CancellationToken ct)
{
  return await db.Orders
    .Where(o => o.UserId == userId)
    .SumAsync(o => o.Total, ct);
}

Watch out

Calling .Result or .Wait() on a Task, which blocks a thread and can starve the pool under load; also avoid async void.

DI container and service lifetimes

Also called DI, dependency injection, IServiceCollection

What
ASP.NET Core has a built-in container. Singleton means one instance for the app, scoped means one per request, transient means a new one every time it is requested.
Why
Handlers declare what they need as parameters and the container supplies it. Lifetimes decide what is shared, which matters for DbContext (scoped) and caches (singleton).
C#
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddTransient<IEmailBuilder, EmailBuilder>();

Watch out

A captive dependency: injecting a scoped DbContext into a singleton, so one context is shared across requests.

Nullable reference types

Also called NRT, string?

What
With nullable reference types on (the default in new projects), string means never null and string? means may be null. The compiler warns when you dereference something that might be null.
Why
It moves the most common runtime crash, NullReferenceException, to a compile-time warning.
C#
string name = "Ada";
string? nickname = null;

int bad = nickname.Length;        // warning CS8602
int safe = nickname?.Length ?? 0;

Watch out

Silencing warnings with ! everywhere instead of handling the null case.

LINQ

Also called Language Integrated Query, IQueryable

What
Query operators like Where, Select and GroupBy over collections. On an EF Core IQueryable the same query is translated to SQL and runs in the database.
Why
You write data access in typed C#, and filtering, grouping and paging happen in the database instead of in memory.
C#
var top = await db.Orders
  .Where(o => o.CreatedAt >= since)
  .GroupBy(o => o.CustomerId)
  .Select(g => new
  {
    CustomerId = g.Key,
    Total = g.Sum(o => o.Total),
  })
  .OrderByDescending(x => x.Total)
  .Take(10)
  .ToListAsync(ct);

Watch out

Calling ToList() or AsEnumerable() before Where, which loads the whole table into memory first.

The dotnet CLI and NuGet

Also called dotnet, NuGet, csproj, dotnet CLI

What
The dotnet command creates, builds, runs and tests projects; NuGet is the package registry, and packages are listed in the .csproj file.
Why
The same commands work on every OS and in CI, and dotnet ef manages Entity Framework Core migrations.
Shell
dotnet new webapi -n Shop.Api
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet ef migrations add Init
dotnet watch run
dotnet test
dotnet publish -c Release

Watch out

Forgetting dotnet tool install --global dotnet-ef, then wondering why dotnet ef is not found.

C# basics: types, classes and records

Also called class, record, struct

What
C# has value types (int, bool, struct) and reference types (class, string). Properties expose data, records give value equality for plain data, and var infers local types.
Why
ASP.NET Core controllers, services and DTOs are all classes and records, so these basics come before any framework.
C#
public record OrderDto(int Id, decimal Total);

public class Order
{
    public int Id { get; init; }
    public decimal Total { get; private set; }
    public void AddItem(decimal price) => Total += price;
}

var a = new OrderDto(1, 40m);
var b = new OrderDto(1, 40m);
Console.WriteLine(a == b); // True: records compare values

Watch out

Using float or double for money instead of decimal.

01 · C# and ASP.NET Core essentials1 / 10