Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JReyna217/PharmaVault/llms.txt

Use this file to discover all available pages before exploring further.

PharmaVault includes a global exception handling middleware that intercepts all unhandled exceptions before they reach the HTTP response pipeline. Every exception — whether anticipated or unexpected — is logged to the error_logs table in PostgreSQL with a UUID incident number generated by the database, and a structured JSON error response is returned to the caller. This means no exception silently disappears, and every production incident can be traced to a specific database record.

ExceptionHandlingMiddleware

The middleware is registered in Program.cs via app.UseMiddleware<ExceptionHandlingMiddleware>(), placing it early in the request pipeline so that exceptions thrown by authentication, authorisation, routing, and Blazor component rendering are all captured.
// Program.cs
app.UseMiddleware<ExceptionHandlingMiddleware>();
When an exception escapes from any downstream middleware or endpoint, control reaches HandleExceptionAsync, which classifies the exception into one of two paths:
If the thrown exception is an ErrorExceptionResponse, the middleware treats it as an expected, developer-defined error. It uses the status code and error code carried by the exception directly, logs the pre-populated Details object, and emits a LogWarning entry.
if (exception is ErrorExceptionResponse controlledEx)
{
    statusCode = controlledEx.StatusCode;
    message = controlledEx.Message;
    incidentNumber = await errorLogDao.LogErrorAsync(controlledEx.Details, userId);

    _logger.LogWarning("Controlled exception {ErrorCode}: {Message}. Incident: {IncidentNumber}",
        controlledEx.ErrorCode, controlledEx.Message, incidentNumber);
}
After classification and logging, both paths converge on the same JSON serialisation step that writes the response body:
context.Response.ContentType = "application/json";
context.Response.StatusCode  = (int)statusCode;

var result = JsonSerializer.Serialize(new
{
    error      = message,
    incidentId = incidentNumber,
    status     = statusCode
});

await context.Response.WriteAsync(result);

ErrorExceptionResponse

ErrorExceptionResponse extends Exception and gives service and DAO code a structured way to raise an error with a specific HTTP status code, a machine-readable error code string, and a fully populated log payload — all in a single throw.
public class ErrorExceptionResponse : Exception
{
    public string ErrorCode { get; }
    public HttpStatusCode StatusCode { get; }
    public ExceptionLogDto Details { get; }

    public ErrorExceptionResponse(
        string errorCode,
        HttpStatusCode statusCode,
        ExceptionLogDto details)
        : base(details.ErrorMessage)
    {
        ErrorCode  = errorCode;
        StatusCode = statusCode;
        Details    = details;
    }
}
To raise a controlled error from anywhere in the application, construct an ErrorExceptionResponse with the desired HTTP status code and a fully described ExceptionLogDto:
throw new ErrorExceptionResponse(
    errorCode:  "MEDICATION_NOT_FOUND",
    statusCode: HttpStatusCode.NotFound,
    details: new ExceptionLogDto
    {
        OriginLayer  = "Service",
        MainObject   = nameof(InventoryService),
        MethodName   = nameof(GetMedicationByIdAsync),
        ErrorMessage = "The requested medication was not found.",
        Description  = $"No inventory record exists for ID {medicationId}."
    }
);
The middleware will catch this, log it, and return a 404 response with the ErrorMessage as the error field in the JSON body.

ExceptionLogDto

ExceptionLogDto is the payload that travels from the throw site to the middleware and then into the database. For controlled exceptions the caller populates it explicitly; for unhandled exceptions the middleware populates it automatically from the exception’s reflection metadata.
public class ExceptionLogDto
{
    public string  OriginLayer  { get; set; } = string.Empty;
    public string  MainObject   { get; set; } = string.Empty;
    public string  MethodName   { get; set; } = string.Empty;
    public string  ErrorMessage { get; set; } = string.Empty;
    public string? Description  { get; set; }
}
FieldPurpose
OriginLayerThe architectural layer where the error originated, e.g. "Service", "Data", or "Backend".
MainObjectThe class name responsible for the error, e.g. "InventoryDao".
MethodNameThe specific method where the error occurred.
ErrorMessageA concise, human-readable description of what went wrong. This value becomes the error field in the JSON response for controlled exceptions.
DescriptionOptional extended detail — used to store the stack trace for unhandled exceptions, or a contextual explanation for controlled ones.

Error Response Format

Every response produced by the middleware follows the same JSON shape, regardless of whether the exception was controlled or unhandled:
{
  "error": "An unexpected internal server error occurred.",
  "incidentId": "550e8400-e29b-41d4-a716-446655440000",
  "status": 500
}
For controlled exceptions, error will contain the ErrorMessage from the ExceptionLogDto, and status will reflect the HttpStatusCode passed to ErrorExceptionResponse. The incidentId is always a UUID corresponding to the row written to error_logs.

error_logs Table

Every exception — controlled or unhandled — is persisted to the error_logs table in PostgreSQL. The incident_number column is a UUID generated by PostgreSQL’s gen_random_uuid() function at insert time via a RETURNING incident_number clause, so the UUID is created atomically with the row and returned to the middleware for inclusion in the response. The ErrorLog model reflects the full table schema:
public class ErrorLog
{
    public int      LogId          { get; set; }
    public string   OriginLayer    { get; set; } = string.Empty;
    public string   MainObject     { get; set; } = string.Empty;
    public string   MethodName     { get; set; } = string.Empty;
    public string?  Description    { get; set; }
    public string   ErrorMessage   { get; set; } = string.Empty;
    public DateTime ErrorDate      { get; set; }
    public int?     UserId         { get; set; }
    public Guid     IncidentNumber { get; set; }
}

IErrorLogDao

The IErrorLogDao interface exposes a single method used by the middleware:
public interface IErrorLogDao
{
    Task<Guid> LogErrorAsync(ExceptionLogDto request, int? userId = null);
}
LogErrorAsync accepts the log payload and an optional user ID, inserts the row, and returns the PostgreSQL-generated incident_number UUID. The concrete implementation (ErrorLogDao) uses Npgsql and raw SQL:
var sql = @"
    INSERT INTO error_logs (origin_layer, main_object, method_name, description, error_message, user_id)
    VALUES (@OriginLayer, @MainObject, @MethodName, @Description, @ErrorMessage, @UserId)
    RETURNING incident_number;";

User Context

The middleware attempts to resolve the authenticated user’s ID before logging. It reads the ClaimTypes.NameIdentifier claim from the current HttpContext.User and, if present and parseable as an integer, passes it to LogErrorAsync as userId. If the request is unauthenticated or the claim is absent, userId is null — the error is still logged, just without a user association.
int? userId = null;
var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (int.TryParse(userIdClaim, out int parsedId))
{
    userId = parsedId;
}
This means every error in error_logs that was produced during an authenticated session carries a user_id, making it straightforward to identify which user encountered a specific class of error.
For Blazor Server apps, unhandled exceptions that occur inside Razor component rendering are also caught by the GlobalErrorBoundary Razor component. The error boundary displays an in-page error message to the user without a full page reload. The HTTP middleware and the error boundary complement each other — the middleware covers API and pipeline-level exceptions while the error boundary handles interactive component exceptions.
Investigate incidents directly in PostgreSQL. Every error response includes an incidentId UUID. To look up the full details of any incident, query the error_logs table:
SELECT *
FROM error_logs
WHERE incident_number = '550e8400-e29b-41d4-a716-446655440000';
The row will contain the origin layer, class name, method name, error message, stack trace (in description), timestamp (error_date), and the associated user_id if the session was authenticated.

Build docs developers (and LLMs) love