Debugging common C# exceptions in modern web applications

Every full-stack developer who has shipped a C# web application has lived through the moment when a red error page replaces what should have been a perfectly normal page load. Exceptions are unavoidable in any non-trivial system, but the difference between a developer who spends three days hunting a NullReferenceException and one who fixes it in twenty minutes usually comes down to a few well-practised habits. In busy Australian engineering teams where release windows are tight and on-call rotations can drag you out of bed at 2am AEST, those habits quickly become career-defining.

The .NET runtime gives you far more information than most developers realise, even when an exception looks like a wall of opaque text. Stack traces, inner exceptions, and structured logs each carry clues. Once you learn to read them in the right order, the wall becomes a map. The goal of this piece is to walk through the exceptions I see most often in ASP.NET web applications, the patterns that usually cause them, and the pragmatic steps I take to track them down.

We won't pretend exceptions disappear once you master them. They will keep appearing in code you didn't write, in third-party libraries, and in production environments that behave differently from your local machine. What you can do is reduce the time between an exception being thrown and you understanding why, so the next red page in your logs feels like a small annoyance rather than an emergency.

NullReferenceException and the silent crashes it causes

NullReferenceException remains the most thrown exception in .NET, decades after the language first introduced nullable types. In ASP.NET, the offender is usually one of three things: a service that was never registered in the dependency injection container, a property on a deserialised model that came back null, or a chain of dot-operations where one link is missing. The default message — "Object reference not set to an instance of an object" — does not tell you which object, but the stack trace usually points straight at the line that tried to dereference it.

The first move I make is to look carefully at the call site. Modern C# with nullable reference types enabled catches most of these issues at compile time, but plenty of Australian codebases are still running on .NET Framework 4.x or pre-C# 8 code that ships without the annotations. If you maintain that kind of codebase, the cheapest upgrade you can make is to turn on <Nullable>enable</Nullable> and start fixing the warnings one file at a time. It pays off within a week.

When you cannot reproduce locally, structured logging saves you. Libraries like Serilog or NLog let you emit exception details with a correlation ID that ties a single request together across services. In production systems I have worked on, ranging from a Sydney fintech to a Brisbane government portal, we made the correlation ID mandatory in middleware and stamped it on every log line, every outgoing HTTP call, and every database command. Without it, debugging a NullReferenceException that fires once in a thousand requests is mostly guesswork.

Defensive coding helps prevent the exception from being thrown in the first place. Null-conditional operators such as user?.Profile?.Email short-circuit before they touch a missing property, and the null-coalescing operator ?? lets you supply a sensible fallback. For method parameters, ArgumentNullException.ThrowIfNull() is a clean way to fail fast at the boundary instead of letting nulls wander deep into your logic.

When the database throws a tantrum

SqlException, TimeoutException, and the various flavours of DbUpdateException from Entity Framework Core cover most database-side failures. Latency to cloud databases hosted in Australian Azure regions (Sydney or Melbourne) is generally low, but cross-region reads to overseas replicas, queries that hit a stretched Always On availability group, or a noisy neighbour on a shared SQL tier can stretch query times well past their usual baseline. A connection that takes thirty seconds to fail often looks like a hang in the application layer rather than a database issue.

The first thing to verify is the connection string itself. A swapped environment variable, a missing credential after a secret rotation in Azure Key Vault, or a typo in the Initial Catalog will produce login failures that masquerade as more dramatic problems. Local developers based in Perth frequently forget that even a connection to a Sydney-region database still traverses the continent, and any local network blip will surface as a SqlException with a generic message.

EF Core offers a developer-mode flag that surfaces the underlying SQL and parameter values for every command. Enable it with EnableSensitiveDataLogging() and EnableDetailedErrors() on your DbContextOptionsBuilder. In production, switch both off but keep a structured log of the inner exception. The wrapped SqlException carries the error number, severity, and state, all of which are searchable online against Microsoft's documentation.

For genuine timeouts, never assume the application is at fault. A missing index in SQL Server can turn a sub-second query into a thirty-second one, and adding logging will not fix the index. Tools like SQL Server's Query Store, the Database Engine Tuning Advisor, or paid options such as SolarWinds Database Performance Analyzer identify slow queries without adding tracing to your application. If you operate in Australia and must comply with the Notifiable Data Breaches scheme, detailed database query logs also help reconstruct what data was touched during an incident.

HTTP and API request errors that catch teams off guard

Async and concurrency pitfalls worth knowing

Reading stack traces like a detective

A stack trace tells a story, but only if you read it bottom-up. The deepest frame is where the exception was actually thrown; everything above is the path the call took to arrive there. When I am working through a tricky one with a colleague over a flat white in a Melbourne laneway café, we usually sketch the call chain on a napkin before touching the keyboard, because the act of drawing it forces us to commit to an order of events.

Filter by exception type first. If the message mentions "Object reference not set to an instance of an object", you know it is a NullReferenceException; you can ignore unrelated frames and focus on the lines inside your own assemblies. For third-party libraries, the stack trace tells you exactly which method to read in the source code. GitHub repositories often have the same line numbers as the compiled DLLs, especially for libraries that publish symbols to nuget.org.

Use ExceptionDispatchInfo.Capture(ex).Throw() when re-throwing to preserve the original stack trace. A bare throw ex; resets it and hides where the error actually originated — a long-standing gotcha for developers who learned C# in the early 2000s. This single habit has saved me hours in more than one production incident at a Sydney-based bank, where every minute of downtime has a real cost attached to it.

Visual Studio, JetBrains Rider, and even VS Code with the C# extension offer excellent exception breakpoints. Set them on the specific exception types you are investigating, and the debugger will pause the moment they fire, even inside framework code. For live production diagnostics, Application Insights (available in the Australian Azure regions) and Seq give you searchable exception histories with full request context, which is invaluable when a customer in Adelaide reports a bug that nobody in Sydney or Melbourne can reproduce.

If you have wrestled with one of these exceptions this week, send me the story through the contact page or open a thread on the GitHub repo linked in the footer. I publish follow-up posts based on real reader questions, and there is a fair chance your particular flavour of NullReferenceException will become the next debugging walk-through.