LSCore Exceptions
The LSCore Exceptions module provides a structured set of exception types that map directly to HTTP status codes. When paired with the exception-handling middleware, thrown exceptions are automatically translated into the appropriate HTTP responses, removing the need for repetitive status-code logic in controllers and services.
Why Exceptions Over Wrapper Classes
There are two common approaches to error handling in APIs:
| Approach | Speed | Complexity | Safety |
|---|---|---|---|
| Wrapper classes | Faster (1-5 ns/request) | Complex | Possible leakage when any part of code throws an unexpected exception |
| Exceptions (LSCore approach) | Slower (1-5 ns/request) | Simple to implement | All unhandled exceptions are converted to 500 without any message leakage |
LSCore uses the exception-based approach because the performance difference is negligible while the simplicity and safety benefits are significant. You throw from anywhere, and the middleware translates it to the correct HTTP response.
NuGet Packages
| Package | Description |
|---|---|
LSCore.Exceptions | Core exception classes. No ASP.NET Core dependency. |
LSCore.Exceptions.DependencyInjection | ASP.NET Core middleware that catches LSCore exceptions and sets the HTTP response status code. Depends on LSCore.Exceptions and the Microsoft.AspNetCore.App framework reference. |
Both packages target .NET 10.0 (version 10.0.0 at time of writing).
Exception Types
All exception types live in the LSCore.Exceptions namespace, extend System.Exception, and offer two constructors: a parameterless constructor and one that accepts a string message.
| Exception Class | HTTP Status Code | Typical Use |
|---|---|---|
LSCoreBadRequestException | 400 Bad Request | The client sent invalid or malformed input. |
LSCoreUnauthenticatedException | 401 Unauthorized | The request lacks valid authentication credentials. |
LSCoreForbiddenException | 403 Forbidden | The authenticated user does not have permission to access the requested resource. |
LSCoreNotFoundException | 404 Not Found | The requested resource does not exist. |
LSCoreInternalException | 500 Internal Server Error | An unexpected server-side error occurred. Handled exactly like any unrecognized exception. |
Detailed Behavior Notes
- The four 4xx exceptions write their
Messageto the response body, so you can surface the reason to the caller. - The body is sent with a
Content-Type:application/json; charset=utf-8when the message is a JSON object or array,text/plain; charset=utf-8otherwise. This is what makes validation errors parseable –Validate()throws its FluentValidation errors as a serialized JSON array, and typed HTTP clients dispatch on the content type. - An empty message produces an empty body and no
Content-Type. - LSCoreInternalException (and any other unhandled exception) results in a 500 with an empty body. The message is logged via
ILoggerat theErrorlevel but never returned, so internal detail cannot leak to the caller. The 4xx paths are not logged – they are expected outcomes, not failures. - An
AggregateExceptionwith a single inner exception (fromTask.WhenAll,.Resultor.Wait()) and aTargetInvocationException(from reflection) are unwrapped first, so a wrappedLSCoreNotFoundExceptionstill returns 404. AnAggregateExceptioncarrying several exceptions has no single status code to map to and becomes a 500. - If the client disconnects mid-request, the resulting
OperationCanceledExceptionis not treated as an error: it is logged atDebugand the response is left alone. - If an exception is thrown after the response has started, the status code can no longer be changed. The exception is logged and rethrown, which aborts the connection so the client sees a truncated response instead of an apparently successful one.
Setting Up the Middleware
1. Install both packages
Add references to LSCore.Exceptions and LSCore.Exceptions.DependencyInjection in your project.
2. Register the middleware
In your Program.cs (or wherever you configure the ASP.NET Core pipeline), call the extension method on WebApplication:
using LSCore.Exceptions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// ... service registration ...
var app = builder.Build();
app.UseLSCoreExceptionsHandler();
// ... other middleware, routing, etc. ...
app.Run();
UseLSCoreExceptionsHandler() inserts LSCoreExceptionsHandleMiddleware into the request pipeline. Place it early enough to catch exceptions from downstream middleware and endpoints.
Code Examples
Throwing exceptions in a service
using LSCore.Exceptions;
public class OrderService
{
public Order GetOrder(int id)
{
var order = _repository.Find(id);
if (order is null)
throw new LSCoreNotFoundException("Order not found.");
return order;
}
public void PlaceOrder(OrderRequest request)
{
if (string.IsNullOrWhiteSpace(request.ProductName))
throw new LSCoreBadRequestException("ProductName is required.");
// ... business logic ...
}
}
Guarding authorization
using LSCore.Exceptions;
public class DocumentService
{
public Document GetDocument(int documentId, User currentUser)
{
if (currentUser is null)
throw new LSCoreUnauthenticatedException();
var document = _repository.Find(documentId);
if (document is null)
throw new LSCoreNotFoundException();
if (document.OwnerId != currentUser.Id)
throw new LSCoreForbiddenException();
return document;
}
}
Signaling an internal failure
using LSCore.Exceptions;
public class PaymentService
{
public void ProcessPayment(PaymentRequest request)
{
var result = _gateway.Charge(request);
if (!result.Success)
throw new LSCoreInternalException("Payment gateway returned an error.");
}
}
How the Middleware Handles Each Exception
When an exception propagates out of the request pipeline, LSCoreExceptionsHandleMiddleware catches it and performs the following:
- Returns immediately (logging at
Debug) if the client has disconnected, or rethrows if the response has already started. - Unwraps single-exception
AggregateExceptions andTargetInvocationExceptions. - Matches the exception type using a
switch. - Sets
context.Response.StatusCodeto the corresponding HTTP status code. - For the four 4xx types, writes
exception.Messageto the response body withapplication/jsonortext/plaindepending on whether the message is JSON. - For
LSCoreInternalExceptionand any other exception, logs atErrorlevel and returns 500 with an empty body.