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
asyncmethod returns aTaskorTask<T>;awaitfrees the thread while I/O is in flight. ACancellationTokenlets 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.
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).
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),
stringmeans never null andstring?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.
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,SelectandGroupByover collections. On an EF CoreIQueryablethe 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.
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
dotnetcommand creates, builds, runs and tests projects; NuGet is the package registry, and packages are listed in the.csprojfile. - Why
- The same commands work on every OS and in CI, and
dotnet efmanages Entity Framework Core migrations.
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 ReleaseWatch 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
varinfers local types. - Why
- ASP.NET Core controllers, services and DTOs are all classes and records, so these basics come before any framework.
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 valuesWatch out
Using float or double for money instead of decimal.