Skip to content

Conversation

@nanotaboada
Copy link
Owner

@nanotaboada nanotaboada commented Dec 4, 2025

Closes #184

Summary by CodeRabbit

  • New Features

    • Implemented global exception handling that returns standardized RFC 7807 problem responses with trace IDs.
    • Environment-aware error details: verbose in development, sanitized in production.
    • Improved handling for validation, argument, concurrency and cancellation errors.
  • Chores

    • Standardized quoting in config files and added an exclusion rule to ignore Middlewares paths.

✏️ Tip: You can customize this high-level summary in your review settings.

- Create ExceptionMiddleware with RFC 7807 Problem Details format
- Map exception types to appropriate HTTP status codes
- Include stack traces in Development environment only
- Add structured logging with Serilog integration
- Register middleware in pipeline after Serilog request logging
- Cache JsonSerializerOptions instance to avoid CA1869 warning
- Exclude Middleware folder from Codecov and Codacy coverage
- Maintain backward compatibility with existing validation handling
- Add HasStarted check before modifying response
- Prevent errors when exception occurs after headers sent
- Log warning when unable to write error response
- Use authoritative Mozilla documentation for error types
@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Walkthrough

Adds a global ExceptionMiddleware that returns RFC 7807 Problem Details for unhandled exceptions, an extension method UseExceptionHandling to register it in the pipeline, and updates CI/config files to use double-quoted patterns and exclude /Middlewares/ from analysis tools.

Changes

Cohort / File(s) Summary
Configuration updates
.codacy.yml, codecov.yml
Converted single-quoted strings to double-quoted strings and added an exclusion pattern for **/Middlewares/**. Minor comment/indentation adjustments.
Middleware implementation
src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
New ExceptionMiddleware that catches exceptions in InvokeAsync, maps common exception types to HTTP status codes (ValidationException→400, ArgumentException/InvalidOperationException→400, DbUpdateConcurrencyException→409, OperationCanceledException→408, others→500), builds RFC 7807 ProblemDetails (type, title, status, detail, instance/traceId), logs structured errors, and writes application/problem+json responses with environment-aware details. Uses camelCase JSON via JsonSerializerOptions.
Pipeline registration
src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs, src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
New MiddlewareExtensions.UseExceptionHandling(this WebApplication app) extension method that calls app.UseMiddleware<ExceptionMiddleware>(). Program.cs updated to invoke app.UseExceptionHandling() after UseSerilogRequestLogging() in the request pipeline.
Controller comment
src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
Added a comment near a PUT logging call referencing log-forging and Serilog structured logging (@ destructuring); no behavior change.

Sequence Diagram

sequenceDiagram
  participant Client
  participant ASP as "ASP.NET Pipeline"
  participant EM as "ExceptionMiddleware"
  participant Logger as "ILogger/Serilog"
  participant Http as "HttpContext"

  Client->>ASP: HTTP Request
  ASP->>EM: Invoke(next) (try)
  alt Handler throws
    EM-->>EM: Map exception → status/title/type
    EM->>Logger: Log error (structured, traceId)
    EM->>Http: Build ProblemDetails (type,title,status,detail,instance)
    EM->>Http: Set Content-Type: application/problem+json
    EM->>Http: Check Response.HasStarted
    alt Not started
      EM->>Http: Write JSON payload
      EM-->>Client: ProblemDetails response (status)
    else Already started
      EM->>Logger: Log warning (response started)
      EM-->>Client: (no modification)
    end
  else No exception
    ASP->>ASP: Continue pipeline
    ASP-->>Client: Normal response
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review exception-to-status mapping and ensure all intended exception types are covered.
  • Verify ProblemDetails fields (type, title, status, detail, instance/traceId) and JSON naming policy.
  • Confirm safe handling when response has already started and correct logging behavior.
  • Check middleware registration order in Program.cs (after Serilog request logging).
  • Validate new exclusion patterns in .codacy.yml and codecov.yml do not unintentionally hide other files.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Changes to .codacy.yml and codecov.yml (quote standardization and exclusion patterns) are tangential housekeeping; the PlayerController comment addition is minor and unrelated to core middleware objectives. Confirm whether configuration file updates and PlayerController comment are intentional scope creep or justified refactoring; clarify if they should be in a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding global exception handling middleware with RFC 7807 support, which matches the main objectives of the PR.
Linked Issues check ✅ Passed All key coding requirements from issue #184 are implemented: ExceptionMiddleware created with exception-to-status mapping, RFC 7807 ProblemDetails responses, MiddlewareExtensions added, middleware registered in Program.cs pipeline, and environment-aware detail levels.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/global-exception-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link

codacy-production bot commented Dec 4, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-1.01% 0.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (6477165) 1152 196 17.01%
Head commit (7980654) 1225 (+73) 196 (+0) 16.00% (-1.01%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#321) 73 0 0.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
.codacy.yml (1)

28-28: Consider whether to exclude Middlewares from static analysis.

Excluding **/Middlewares/** from Codacy analysis means the new ExceptionMiddleware won't be analyzed for code quality issues. Since this middleware is a core infrastructure component handling all unhandled exceptions, consider whether static analysis should still apply to catch potential issues.

src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (1)

88-90: Redundant pattern match for ArgumentNullException.

ArgumentNullException inherits from ArgumentException, so it will already match the ArgumentException case. The explicit ArgumentNullException arm is unreachable.

         return exception switch
         {
             ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"),
-            ArgumentException
-            or ArgumentNullException
-                => (StatusCodes.Status400BadRequest, "Bad Request"),
+            ArgumentException => (StatusCodes.Status400BadRequest, "Bad Request"),
             InvalidOperationException => (StatusCodes.Status400BadRequest, "Invalid Operation"),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6477165 and 7801075.

📒 Files selected for processing (5)
  • .codacy.yml (1 hunks)
  • codecov.yml (3 hunks)
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs (1 hunks)
  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (1 hunks)
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Use PascalCase for class names, methods, and public properties in C#
Use camelCase for local variables and private fields in C#
Use async/await consistently for asynchronous code in C#
Prefer var for local variable declarations where the type is obvious in C#
Format code with CSharpier formatting standards
Use Dependency Injection for all services and repositories in ASP.NET Core
Write EF Core queries using LINQ with async operations (e.g., FirstOrDefaultAsync instead of FirstOrDefault)
Use AsNoTracking() for read-only EF Core queries to improve performance
Use ILogger<T> for structured logging in ASP.NET Core
Do not use EF Core synchronous APIs (use async alternatives like FirstOrDefaultAsync)
Do not use ConfigureAwait(false) in ASP.NET Core contexts
Use DbContextPool configuration for improved EF Core performance in ASP.NET Core

Files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
🧠 Learnings (4)
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/Controllers/**/*.cs : Implement consistent error response formats in ASP.NET Core controllers

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Avoid adding unnecessary middleware or filters without clear purpose in ASP.NET Core

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/*.cs : Use `ILogger<T>` for structured logging in ASP.NET Core

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/Services/**/*.cs : Use Service Layer for business logic encapsulation in ASP.NET Core

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
🧬 Code graph analysis (1)
src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs (1)
src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (1)
  • ExceptionMiddleware (11-117)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: coverage (codacy)
  • GitHub Check: coverage (codecov)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (8)
codecov.yml (1)

62-62: Verify intent to exclude Middlewares from code coverage.

The PR objectives mention adding unit tests for the middleware. Excluding Middlewares/ from Codecov means any tests written won't contribute to coverage metrics. If unit tests are planned (per issue #184), consider removing this exclusion to track middleware test coverage.

src/Dotnet.Samples.AspNetCore.WebApi/Program.cs (1)

57-59: LGTM!

The middleware is correctly positioned after UseSerilogRequestLogging() (ensuring request logging is captured) and early enough in the pipeline to catch exceptions from downstream middleware like UseHttpsRedirection(), UseRateLimiter(), and MapControllers(). Based on learnings, this middleware has a clear purpose for centralized exception handling.

src/Dotnet.Samples.AspNetCore.WebApi/Extensions/MiddlewareExtensions.cs (1)

1-21: LGTM!

Clean implementation following ASP.NET Core conventions. The extension method pattern is appropriate, XML documentation is thorough, and returning WebApplication enables fluent chaining. This aligns with the codebase's existing extension method patterns in the Extensions namespace.

src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (5)

1-16: LGTM!

Good use of C# primary constructor syntax for dependency injection. Static JsonOptions with camelCase naming is appropriate for JSON serialization. Using ILogger<ExceptionMiddleware> aligns with coding guidelines for structured logging.


21-31: LGTM!

Standard ASP.NET Core middleware pattern with proper async/await usage. The try-catch wrapping next(context) correctly catches exceptions from all downstream middleware and handlers.


36-78: Well-structured exception handling with RFC 7807 compliance.

The implementation correctly:

  • Builds ProblemDetails with required fields (type, title, status, detail, instance)
  • Adds traceId for request correlation
  • Uses structured logging with the exception object
  • Guards against HasStarted before writing the response

103-116: LGTM!

Environment-aware detail generation is a good security practice. Development includes full stack traces for debugging, while production sanitizes sensitive information. The pattern matching for specific exception types in production ensures validation errors remain informative.


93-93: Reconsider mapping OperationCanceledException to 408 without distinguishing causation.

HTTP 408 Request Timeout is semantically correct only when the server times out. OperationCanceledException can arise from two different sources:

  • Client disconnect (HttpContext.RequestAborted): the exception occurs because the client closed the connection mid-request. In this case, you cannot send a response at all—attempting to write 408 will fail.
  • Server-side timeout: your code's cancellation token was triggered by a server timeout. Here, 408 is appropriate if response hasn't started.

The current static MapExceptionToStatusCode method lacks HttpContext access, so it cannot differentiate. Either:

  1. Pass HttpContext to detect HttpContext.RequestAborted.IsCancellationRequested and branch accordingly (no response for client disconnect, 408 for server timeout).
  2. Stop catching OperationCanceledException altogether and let Kestrel handle it (avoids the incorrect 408 for client disconnects).
  3. Map to 500 if causation is unknown.

Note: HTTP 499 is non-standard and only used by proxies/infrastructure for logging; do not return it from your application.

Add suppression comments trusting Serilog's @ destructuring to automatically
escape control characters, avoiding redundant sanitization.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 5, 2025

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (2)

85-95: Minor: ArgumentNullException is redundant in the pattern.

ArgumentNullException derives from ArgumentException, so the explicit or ArgumentNullException clause is unnecessary—the ArgumentException case already catches it.

Additionally, consider whether OperationCanceledException should always map to 408. If the client disconnects mid-request, the cancellation token fires, but 408 implies the server timed out waiting for the client. Some APIs use 499 (nginx-style) or simply don't respond. This is acceptable if intentional.

         return exception switch
         {
             ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"),
-            ArgumentException
-            or ArgumentNullException
-                => (StatusCodes.Status400BadRequest, "Bad Request"),
+            ArgumentException => (StatusCodes.Status400BadRequest, "Bad Request"),
             InvalidOperationException => (StatusCodes.Status400BadRequest, "Invalid Operation"),
             DbUpdateConcurrencyException => (StatusCodes.Status409Conflict, "Concurrency Conflict"),
             OperationCanceledException => (StatusCodes.Status408RequestTimeout, "Request Timeout"),
             _ => (StatusCodes.Status500InternalServerError, "Internal Server Error")
         };

103-116: Handle potentially null StackTrace.

Exception.StackTrace can be null in edge cases (e.g., exception created but not thrown). Consider null-coalescing to avoid "null" appearing in the output.

     private string GetExceptionDetail(Exception exception)
     {
         if (environment.IsDevelopment())
         {
-            return $"{exception.Message}\n\nStack Trace:\n{exception.StackTrace}";
+            return $"{exception.Message}\n\nStack Trace:\n{exception.StackTrace ?? "N/A"}";
         }

         return exception switch
         {
             ValidationException => exception.Message,
             ArgumentException => exception.Message,
             _ => "An unexpected error occurred while processing your request."
         };
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7801075 and 7980654.

📒 Files selected for processing (2)
  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs (1 hunks)
  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Use PascalCase for class names, methods, and public properties in C#
Use camelCase for local variables and private fields in C#
Use async/await consistently for asynchronous code in C#
Prefer var for local variable declarations where the type is obvious in C#
Format code with CSharpier formatting standards
Use Dependency Injection for all services and repositories in ASP.NET Core
Write EF Core queries using LINQ with async operations (e.g., FirstOrDefaultAsync instead of FirstOrDefault)
Use AsNoTracking() for read-only EF Core queries to improve performance
Use ILogger<T> for structured logging in ASP.NET Core
Do not use EF Core synchronous APIs (use async alternatives like FirstOrDefaultAsync)
Do not use ConfigureAwait(false) in ASP.NET Core contexts
Use DbContextPool configuration for improved EF Core performance in ASP.NET Core

Files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
**/Controllers/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/Controllers/**/*.cs: Generate idiomatic ASP.NET Core controller actions with proper HTTP status codes (200, 201, 400, 404, 409, 500)
Use proper HTTP verbs (GET, POST, PUT, DELETE) in ASP.NET Core controller routes
Return appropriate status codes (200, 201, 400, 404, 409, 500) from ASP.NET Core controllers
Implement consistent error response formats in ASP.NET Core controllers
Use route parameters for resource identification in ASP.NET Core APIs

Files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
🧠 Learnings (4)
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/Controllers/**/*.cs : Use proper HTTP verbs (GET, POST, PUT, DELETE) in ASP.NET Core controller routes

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/Controllers/**/*.cs : Generate idiomatic ASP.NET Core controller actions with proper HTTP status codes (200, 201, 400, 404, 409, 500)

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Avoid adding unnecessary middleware or filters without clear purpose in ASP.NET Core

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
📚 Learning: 2025-11-25T18:04:01.330Z
Learnt from: CR
Repo: nanotaboada/Dotnet.Samples.AspNetCore.WebApi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T18:04:01.330Z
Learning: Applies to **/Controllers/**/*.cs : Implement consistent error response formats in ASP.NET Core controllers

Applied to files:

  • src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: coverage (codecov)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (2)
src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs (1)

192-193: LGTM! Good defensive documentation.

The CodeQL suppression comment appropriately addresses the log-forging concern for logging user-provided input. The explanation that Serilog's @ destructuring operator escapes control characters is accurate and helps future maintainers understand why this is safe.

For consistency, you might consider whether line 45 (logging validation errors derived from user input) would benefit from a similar comment, though FluentValidation's error messages are already sanitized.

src/Dotnet.Samples.AspNetCore.WebApi/Middlewares/ExceptionMiddleware.cs (1)

36-78: Exception handling implementation looks solid.

The method correctly implements RFC 7807 Problem Details with all required fields. Good defensive check for HasStarted before writing the response, and appropriate fallback logging when the response has already started.

The log forging concern on line 57 (flagged by code scanning) is mitigated by Serilog's structured logging, which automatically escapes control characters when using message templates with placeholders.

@nanotaboada nanotaboada merged commit 48ddb14 into master Dec 5, 2025
16 checks passed
@nanotaboada nanotaboada deleted the feature/global-exception-handling branch December 5, 2025 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add custom middleware for global exception handling

2 participants