C# .NET Core Basics Cheat Sheet
Introduces ASP.NET Core and .NET fundamentals: Program.cs setup, dependency injection lifetimes, configuration, and the dotnet CLI workflow.
Minimal Program.cs
The entry point and hosting setup for a modern .NET app.
var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();builder.Services.AddScoped<IOrderService, OrderService>(); // DI registrationvar app = builder.Build();app.MapGet("/health", () => "OK");app.MapControllers();app.Run();
Dependency Injection Lifetimes
Singleton, scoped, and transient service registration.
// Program.csbuilder.Services.AddSingleton<ICacheService, MemoryCacheService>(); // One instance for app lifetimebuilder.Services.AddScoped<IUserRepository, UserRepository>(); // One instance per requestbuilder.Services.AddTransient<IEmailSender, EmailSender>(); // New instance every resolution// Injected via constructorpublic class OrderService{ private readonly IUserRepository _repo; public OrderService(IUserRepository repo) => _repo = repo;}
Configuration & Environments
Reading settings and branching on environment.
// appsettings.json: { "ConnectionStrings": { "Default": "..." } }var connStr = builder.Configuration.GetConnectionString("Default");var apiKey = builder.Configuration["ExternalApi:Key"];if (builder.Environment.IsDevelopment()){ app.UseDeveloperExceptionPage();}
Core Concepts
Building blocks every .NET Core project relies on.
- dotnet CLI- dotnet new webapi, dotnet build, dotnet run, dotnet test, dotnet publish
- Project SDK- <Project Sdk="Microsoft.NET.Sdk.Web"> in the .csproj defines the project type
- Middleware pipeline- app.Use...() calls run in order for every request (auth, routing, CORS, etc.)
- appsettings.json- Layered config; appsettings.{Environment}.json overrides the base settings file
- Dependency Injection- Built into the framework via IServiceCollection; no third-party container required
- NuGet- dotnet add package <Name> adds a dependency to the .csproj
- Hosted services- IHostedService/BackgroundService for long-running background tasks
Options Pattern
Strongly-typed, bindable, and reloadable configuration sections.
public class SmtpOptions{ public const string Section = "Smtp"; public string Host { get; set; } = string.Empty; public int Port { get; set; }}// Program.csbuilder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection(SmtpOptions.Section));// Consumers pick the shape that matches their lifetime needspublic class MailSender{ private readonly SmtpOptions _snapshot; // IOptions<T> -> singleton, read once at startup // IOptionsSnapshot<T> -> scoped, re-read per request // IOptionsMonitor<T> -> singleton, supports OnChange callbacks for live reload public MailSender(IOptionsSnapshot<SmtpOptions> options) => _snapshot = options.Value;}
Custom Middleware
Writing a conventional middleware class instead of an inline lambda.
public class RequestTimingMiddleware{ private readonly RequestDelegate _next; private readonly ILogger<RequestTimingMiddleware> _logger; public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); await _next(context); // Call the next component in the pipeline sw.Stop(); _logger.LogInformation("{Path} took {Elapsed}ms", context.Request.Path, sw.ElapsedMilliseconds); }}// Registration extensionpublic static class MiddlewareExtensions{ public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app) => app.UseMiddleware<RequestTimingMiddleware>();}// Program.csapp.UseRequestTiming();
Typed HttpClient with IHttpClientFactory
Avoids socket exhaustion and centralizes resilience policies for outbound calls.
public class WeatherApiClient{ private readonly HttpClient _http; public WeatherApiClient(HttpClient http) => _http = http; public Task<WeatherDto?> GetCurrentAsync(string city) => _http.GetFromJsonAsync<WeatherDto>($"/v1/current?city={city}");}// Program.csbuilder.Services.AddHttpClient<WeatherApiClient>(client =>{ client.BaseAddress = new Uri("https://api.weather.example"); client.Timeout = TimeSpan.FromSeconds(5);}).AddStandardResilienceHandler(); // Built-in retry + circuit breaker + timeout (Microsoft.Extensions.Http.Resilience)
BackgroundService
A long-running hosted task that starts with the app and respects graceful shutdown.
public class QueueDrainService : BackgroundService{ private readonly IServiceScopeFactory _scopeFactory; public QueueDrainService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _scopeFactory.CreateScope(); // Scoped services need a manual scope here var repo = scope.ServiceProvider.GetRequiredService<IOrderRepository>(); await repo.ProcessPendingAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } }}// Program.csbuilder.Services.AddHostedService<QueueDrainService>();
Advanced Hosting Concepts
Production-facing pieces of the ASP.NET Core hosting model beyond the basic pipeline.
- Kestrel- The cross-platform web server built into .NET; usually run behind a reverse proxy (Nginx/IIS/YARP) in production
- Graceful shutdown- IHostApplicationLifetime.ApplicationStopping lets in-flight requests finish before the process exits
- Health checks- AddHealthChecks()/MapHealthChecks("/health") expose liveness and readiness probes for orchestrators
- Rate limiting- Microsoft.AspNetCore.RateLimiting middleware enforces fixed-window, sliding-window, or token-bucket limits
- Output caching- AddOutputCache()/CacheOutput() caches full responses server-side, distinct from response caching headers
- Logging providers- ILogger fans out to Console, Debug, EventSource, and third-party providers (Serilog, OpenTelemetry) simultaneously
- IOptionsMonitor<T>- Reacts to config file changes at runtime via OnChange, without restarting the app
- Environment detection- ASPNETCORE_ENVIRONMENT drives IWebHostEnvironment.IsDevelopment()/IsProduction() branching
Prefer AddScoped for anything that touches a DbContext or per-request state — injecting a scoped service into a singleton creates a captive dependency bug that's hard to diagnose.