Skip to main content

Error Handling and Exception Middleware

Level: Beginner to Intermediate

ℹ️ Where This Fits

Error handling teaches how your app should respond when something fails. This is essential before deploying APIs to real users.

ℹ️ What You'll Learn
  • Error handling purpose: Gracefully handle failures, log issues, return helpful response to client
  • Expected errors: Validation errors (missing name), business logic errors (student already exists)
  • Unexpected exceptions: Null reference, database connection failed, code bug
  • Development error response: Detailed stack trace (helps debugging), shows SQL query, line number
  • Production error response: Generic "An error occurred" (don't expose internals), log details server-side
  • app.UseExceptionHandler("/error"): Middleware catches all unhandled exceptions, redirects to error endpoint
  • Exception filter: [ApiControllerAttribute] automatically catches exceptions, returns 500 with problem details
  • try-catch pattern: Catch expected exceptions, throw custom exceptions for validation errors
  • Logging exceptions: _logger.LogError(ex, "Failed to create student") includes exception + message
  • Custom exception types: throw new StudentNotFoundException("Student not found") vs generic exception
  • Error response format: Problem Details (RFC 7807) with type, title, status, detail fields
  • School Management errors: Student 404, duplicate roll number 409, database error 500
  • Client error codes: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found)
  • Server error codes: 500 (internal error), 503 (service unavailable), 504 (timeout)
  • Common mistakes: Returning 200 with error message inside JSON (client thinks success!), exposing stack traces in production
  • Common mistakes in production error handling

Why Error Handling Matters

Every real application fails sometimes.

Examples in a school system:

  • database connection fails
  • student id does not exist
  • parent sends invalid payment data
  • file upload is too large
  • SMS provider is down
  • exam result publishing throws an exception

Good error handling does two things:

User sees a safe, understandable response.
Developer gets enough logs to fix the issue.

Expected Errors vs Unexpected Exceptions

Not every failure is an exception.

SituationTypeResponse
Student not foundExpected404 NotFound
Invalid admission formExpected400 BadRequest
User not logged inExpected401 Unauthorized
Database connection failedUnexpected500 Internal Server Error
Null reference bugUnexpected500 Internal Server Error

Use action results for expected errors.

Use exception handling middleware for unexpected failures.

Development vs Production

Development should show details to help developers.

Production should hide technical details from users.

EnvironmentError Behavior
DevelopmentDetailed exception page
ProductionGeneric error response and server-side logs

Basic Error Pipeline

💻 Try It — Console App
💡 Use developer exception page only in Development.⌥ GitHub
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}

app.MapControllers();

app.Run();

Important rule:

Do not show developer exception pages in production.

API Error Endpoint

For APIs, the /error endpoint can return JSON.

using Microsoft.AspNetCore.Diagnostics;

app.Map("/error", (HttpContext context, ILogger<Program> logger) =>
{
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = feature?.Error;

if (exception != null)
{
logger.LogError(
exception,
"Unhandled exception on path {Path}",
feature?.Path);
}

return Results.Problem(
title: "An unexpected error occurred.",
statusCode: StatusCodes.Status500InternalServerError);
});

This logs the real exception but returns a safe response.

ProblemDetails

ProblemDetails is a standard error response format for APIs.

Example response:

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An unexpected error occurred.",
"status": 500
}

It gives API clients a predictable structure.

Expected Error Example

Do not throw exceptions for normal cases like "student not found".

[HttpGet("{id}")]
public IActionResult GetStudent(int id)
{
if (id <= 0)
{
return BadRequest(new { message = "Invalid student id." });
}

var student = _studentService.GetStudent(id);

if (student == null)
{
return NotFound(new { message = "Student not found." });
}

return Ok(student);
}

This is not exception handling. This is normal API response handling.

Try-Catch in Actions

Use try-catch when you can handle the error meaningfully.

[HttpPost("publish-results")]
public IActionResult PublishResults(PublishResultsRequest request)
{
try
{
_resultService.Publish(request.ExamId);
return Ok(new { message = "Results published." });
}
catch (ExamNotFinalizedException)
{
return BadRequest(new { message = "Exam is not finalized yet." });
}
}

If you cannot handle the exception properly, let global middleware handle it.

Logging Exceptions

Always pass the exception object to LogError.

catch (Exception ex)
{
_logger.LogError(
ex,
"Failed to publish results for exam {ExamId}",
request.ExamId);

return StatusCode(500, new
{
message = "Unable to publish results right now."
});
}

This keeps stack trace information in logs.

What Not to Show Users

ℹ️ Production Safety

In production, never expose stack traces, SQL errors, connection strings, file paths, secret keys, or internal class names to users.

Bad response:

SqlException: Login failed for user...
ConnectionString=Server=...
at School.Data.StudentRepository...

Better response:

{
"message": "Unable to process the request right now."
}

Custom Exception Types

Custom exceptions can represent business failures.

public class AdmissionClosedException : Exception
{
public AdmissionClosedException()
: base("Admissions are closed for this class.")
{
}
}

Then handle it:

catch (AdmissionClosedException ex)
{
return BadRequest(new { message = ex.Message });
}

Do not create custom exceptions for every small validation rule.

Middleware vs Filters vs Try-Catch

ApproachBest For
Try-catchExpected specific failure inside one action
Exception filterController-specific exception handling
Exception middlewareGlobal unhandled exception handling

For most APIs:

Use action results for expected errors.
Use global exception middleware for unexpected errors.
Use logging everywhere important.

Common Mistakes

MistakeBetter Approach
Showing stack traces in productionReturn safe generic errors
Catching exceptions and ignoring themLog useful context
Throwing exceptions for validation errorsReturn BadRequest
Returning 200 OK for failuresUse correct status codes
Logging passwords/tokensNever log secrets
Handling every exception in every actionUse middleware for global failures

Practice Task

Create error handling for a school API:

  1. Add UseDeveloperExceptionPage() only for Development.
  2. Add UseExceptionHandler("/error") for Production.
  3. Create /error endpoint returning Results.Problem.
  4. Log the exception path and message.
  5. Return NotFound for missing students without throwing.

Quick Recap

QuestionAnswer
Dev detailed errors?UseDeveloperExceptionPage()
Production global handler?UseExceptionHandler()
Standard API error shape?ProblemDetails
Missing record response?404 NotFound
Invalid input response?400 BadRequest
🎯 Interview Favourite

Q: How do you handle errors in ASP.NET Core?

Good Answer: "In ASP.NET Core, expected errors such as validation failures or missing records should be returned using proper action results like BadRequest and NotFound. Unexpected exceptions should be handled by global exception middleware using UseExceptionHandler. In development, UseDeveloperExceptionPage can show detailed errors, but in production the app should return safe generic responses and log full exception details with ILogger. APIs commonly use ProblemDetails for standard error responses."

🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on ASP.NET Core Error Handling. Try these prompts:

  • "Explain expected errors vs unexpected exceptions in ASP.NET Core."
  • "Show me production-safe exception middleware for Web API."
  • "What should never be shown in production error responses?"
  • "When should I use try-catch instead of global exception middleware?"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

Next Article

-> CORS - Cross-Origin Resource Sharing

nexcoding.in