Returning a raw, unhandled exception's message to a client leaks internal implementation details (stack traces, connection strings in an exception message, internal type names) and gives the caller nothing structured to handle programmatically. The standard fix is centralized exception-handling middleware that converts any unhandled exception into a consistent ProblemDetails response:
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage(); // detailed errors, safe ONLY for local development
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
var problem = new ProblemDetails
{
Status = 500,
Title = "An unexpected error occurred.",
// deliberately no exception message/stack trace exposed to the client
};
await context.Response.WriteAsJsonAsync(problem);
});
});
}
ProblemDetails (from RFC 7807) is the standardized JSON shape ASP.NET Core already uses automatically for [ApiController] validation failures — reusing it for unhandled exceptions too keeps every error response from the API in one consistent, machine-parseable shape, rather than validation errors looking nothing like server errors.