{"id":25290960,"url":"https://github.com/salixzs/aspnetcore.jsonexceptionhandler","last_synced_at":"2025-04-06T19:17:32.375Z","repository":{"id":64840598,"uuid":"572991037","full_name":"salixzs/AspNetCore.JsonExceptionHandler","owner":"salixzs","description":"Exception handler middleware in ASP.NET (API solutions mainly) to get exception as JSON object with rfc7807 standard proposal in mind.","archived":false,"fork":false,"pushed_at":"2024-02-21T23:20:15.000Z","size":91,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-14T04:17:03.796Z","etag":null,"topics":["aspnetcore","aspnetcorewebapi","error-handling","exception","exception-handler","middleware"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salixzs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-12-01T13:20:37.000Z","updated_at":"2024-01-15T10:17:04.000Z","dependencies_parsed_at":"2024-01-11T23:20:26.538Z","dependency_job_id":null,"html_url":"https://github.com/salixzs/AspNetCore.JsonExceptionHandler","commit_stats":{"total_commits":10,"total_committers":1,"mean_commits":10.0,"dds":0.0,"last_synced_commit":"557bc4e81f97a989f4c1f68893511b40694c29f1"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salixzs%2FAspNetCore.JsonExceptionHandler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salixzs%2FAspNetCore.JsonExceptionHandler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salixzs%2FAspNetCore.JsonExceptionHandler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salixzs%2FAspNetCore.JsonExceptionHandler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salixzs","download_url":"https://codeload.github.com/salixzs/AspNetCore.JsonExceptionHandler/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247535516,"owners_count":20954576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnetcore","aspnetcorewebapi","error-handling","exception","exception-handler","middleware"],"created_at":"2025-02-13T00:51:22.140Z","updated_at":"2025-04-06T19:17:32.353Z","avatar_url":"https://github.com/salixzs.png","language":"C#","readme":"# AspNetCore.JsonExceptionHandler\nProduction (and Debug) replacement for `app.UseDeveloperExceptionPage()`.\nException handler middleware in ASP.NET (API solutions mainly) to get exception as JSON object with rfc7807 standard proposal in mind.\nImplementing provided abstract class with simplistic your own middleware gives ability to handle specific exceptions and control retuerned state codes (400+; 500+) with Json data payload, describing error situation and throw exception(s).\n\n[![Build \u0026 Tests](https://github.com/salixzs/AspNetCore.JsonExceptionHandler/actions/workflows/build_test.yml/badge.svg?branch=main)](https://github.com/salixzs/AspNetCore.JsonExceptionHandler/actions/workflows/build_test.yml)\n[![Nuget version](https://img.shields.io/nuget/v/Salix.AspNetCore.JsonExceptionHandler.svg)](https://www.nuget.org/packages/Salix.AspNetCore.JsonExceptionHandler/)\n[![NuGet Downloads](https://img.shields.io/nuget/dt/Salix.AspNetCore.JsonExceptionHandler.svg)](https://www.nuget.org/packages/Salix.AspNetCore.JsonExceptionHandler/) (since 15-Dec-2022)\n\n#### If you use or like...\n\nCosider \"star\" this project and/or better\\\n\u003ca href=\"https://www.buymeacoffee.com/salixzs\" target=\"_blank\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" style=\"height: 32px !important;width: 146px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;\" \u003e\u003c/a\u003e\n\nSee also other packages for some other/related functionality in Asp.Net Core (mostly APIs):\n- [API dynamic FrontPage (with binaries versioning approaches)](https://www.nuget.org/packages/Salix.AspNetCore.FrontPage/)\n- [Health check with JSON result + Health page](https://www.nuget.org/packages/Salix.AspNetCore.HealthCheck/)\n- [Configuration validation](https://www.nuget.org/packages/ConfigurationValidation.AspNetCore/)\n\n## Usage\n\nPackage includes most basic implementation of abstract class, ready to use right away, which can be wired up by adding `app.AddJsonExceptionHandler();` into `program.cs` (or `startup.cs` if you use older approach).\\\nThis will return state code 500 with Json object.\n\nMore advanced way is to add your own middleware based on provided abstract base class as in this example (example mimics included default middleware):\n\n```csharp\n/// \u003csummary\u003e\n/// Own middleware with provided base middleware class.\n/// \u003c/summary\u003e\npublic class ApiJsonErrorMiddleware : ApiJsonExceptionMiddleware\n{\n    // use either this simplified constructor\n    public ApiJsonErrorMiddleware(RequestDelegate next, ILogger\u003cApiJsonExceptionMiddleware\u003e logger, bool showStackTrace)\n        : base(next, logger, showStackTrace)\n    {\n    }\n    \n    // or use this constructor to supply extended options\n    public ApiJsonErrorMiddleware(RequestDelegate next, ILogger\u003cApiJsonExceptionMiddleware\u003e logger, ApiJsonExceptionOptions options)\n        : base(next, logger, options)\n    {\n    }\n}\n```\n\nAfter it is created, you can register it in API `Program.cs` (or `Startup.cs` `Configure` method) like this (somewhere in the very beginning setup for ­`app`):\n\n```csharp\n// When used constructor with options and relaying on default settings:\napp.AddJsonExceptionHandler\u003cApiJsonErrorMiddleware\u003e();\n\n// When used constructor with boolean:\napp.AddJsonExceptionHandler\u003cApiJsonErrorMiddleware\u003e(true);\n\n// When used with options setting:\napp.AddJsonExceptionHandler\u003cApiJsonErrorMiddleware\u003e(new ApiJsonExceptionOptions { OmitSources = new HashSet\u003cstring\u003e { \"SomeMiddleware\" }, ShowStackTrace = true });\n```\n\nThe only parameter in simple constructor controls whether StackTrace is shown to consumer.\\\nIn example above we can control it by environment variable so it is shown during API development, but hidden in any other environment. If you put constant true/false in stead - it is either shown always or hidden always.\n\nFor options - you can set the same `showStackTrace` boolean and also specify list of stack trace frames to be filtered out from being shown. It is `OmitSources` property, containing list (HashSet) of strings, which should not be a part of file path in stack trace frame.\nFor example, if you set it to `new HashSet\u003cstring\u003e { \"middleware\" }`, it will filter out all middleware components (given they have string \"middleware\" in their file name or in path).\n\n### Custom exception handling\nIf you want to handle (return data on) some specific exceptions, then you should override `HandleSpecialException` method from base class. There you can check whether exception is of this special type and modify returned Json data structure accordingly:\n\n```csharp\n/// \u003csummary\u003e\n/// This method is called from base class handler to add more information to Json Error object.\n/// Here all special exception types should be handled, so API Json Error returns appropriate data.\n/// \u003c/summary\u003e\n/// \u003cparam name=\"apiError\"\u003eApiError object, which gets returned from API in case of exception/error. Provided by \u003c/param\u003e\n/// \u003cparam name=\"exception\"\u003eException which got bubbled up from somewhere deep in API logic.\u003c/param\u003e\nprotected override ApiError HandleSpecialException(ApiError apiError, Exception exception)\n{\n    // When using FluentValidation, could use also handler for its ValidationException in stead of this custom one\n    if (exception is SampleDataValidationException validationException)\n    {\n        apiError.Status = 400; // or 422\n        apiError.ErrorType = ApiErrorType.DataValidationError;\n        apiError.ValidationErrors\n            .AddRange(\n                validationException.ValidationErrors.Select(failure =\u003e\n                    new ApiDataValidationError\n                    {\n                        Message = failure.ValidationMessage,\n                        PropertyName = failure.PropertyName,\n                        AttemptedValue = failure.AppliedValue\n                    }));\n        // This does not log error (e.g. Not show up in ApplicationInsights), but still returns Json error.\n        apiError.ErrorBehavior = ApiErrorBehavior.RespondWithError;\n    }\n\n    if (exception is AccessViolationException securityException)\n    {\n        apiError.Status = 401; // or 403\n        apiError.ErrorType = ApiErrorType.AccessRestrictedError;\n    }\n\n    if (exception is SampleDatabaseException dbException)\n    {\n        apiError.Status = 500;\n        if (dbException.ErrorType == DatabaseProblemType.WrongSyntax)\n        {\n            apiError.ErrorType = ApiErrorType.StorageError;\n        }\n    }\n\n    if (exception is NotImplementedException noImplemented)\n    {\n        apiError.Status = 501;\n        apiError.Title = \"Functionality is not yet implemented.\";\n    }\n    \n    if (exception is OperationCanceledException operationCanceledException)\n    {\n        // This returns empty (200) response and does not log error.\n        apiError.ErrorBehavior = ApiErrorBehavior.Ignore;\n    }\n\n    return apiError;\n}\n```\n\n\nIn case of data validation exceptions, when they are handled fully (as shown in example above), Json property `validationErrors` is provided:\n\n```json\n{\n    \"type\": \"DataValidationError\",\n    \"title\": \"There are validation errors.\",\n    \"status\": 400,\n    \"requestedUrl\": \"/api/sample/validation\",\n    \"errorType\": 3,\n    \"exceptionType\": \"SampleDataValidationException\",\n    \"innerException\": {\n      \"title\": \"Some inner exception\",\n      \"exceptionType\": \"ArgumentNullException\",\n      \"innerException\": {\n        \"title\": \"Deepest inner exception\",\n        \"exceptionType\": \"NotImplementedException\",\n        \"innerException\": null\n      }\n    },\n    \"stackTrace\": [\n        \"at ValidationError() in Sample.AspNet5.Logic\\\\SampleLogic.cs: line 50\",\n        \"at ThrowValidationException() in Sample.AspNet5.Api\\\\Services\\\\HomeController.cs: line 117\",\n        \"at Invoke(HttpContext httpContext) in Source\\\\Salix.ExceptionHandling\\\\ApiJsonExceptionMiddleware.cs: line 56\"\n    ],\n    \"validationErrors\": [\n        {\n            \"propertyName\": \"Name\",\n            \"attemptedValue\": \"\",\n            \"message\": \"Missing/Empty\"\n        },\n        {\n            \"propertyName\": \"Id\",\n            \"attemptedValue\": null,\n            \"message\": \"Cannot be null\"\n        },\n        {\n            \"propertyName\": \"Description\",\n            \"attemptedValue\": \"Lorem Ipsum very long...\",\n            \"message\": \"Text is too long\"\n        },\n        {\n            \"propertyName\": \"Birthday\",\n            \"attemptedValue\": \"2054-06-22T23:55:26.1708087+03:00\",\n            \"message\": \"Cannot be in future\"\n        }\n    ]\n}\n```\n\n## Behaviour control\nBy default Json error handler will write exception to configured `ILogger` instance (you control where and how it writes - AppInsights, File, Debug, Console etc.)\\\nand also creates Json error response and returns it to caller with specified HttpStatus code (400+, 500+).\n\nIf you use custom exception handler method, you can intercept specific exceptions and make error handler do not write an error statement to `ILogger` and/or return Json error object at all (returns 200 status code with empty response).\n\nTo control it, in specific exception handling method, intercept your special exception and set `ApiError` object property `ErrorBehavior` to desired behaviour.\n\n```csharp\nif (exception is OperationCanceledException operationCanceledException)\n{\n    // This returns empty (200) response and does not log error.\n    apiError.ErrorBehavior = ApiErrorBehavior.Ignore;\n}\n\nif (exception is TaskCanceledException taskCanceledException)\n{\n    // This does not log error, but still returns Json error.\n    apiError.ErrorBehavior = ApiErrorBehavior.RespondWithError;\n    apiError.Status = (int)HttpStatusCode.UnprocessableEntity; // or other by your design\n    apiError.ErrorType = ApiErrorType.CancelledOperation;\n}\n```\n\nIt could come handy to ignore user cancelled operations when using async code with CancellationToken.\n\n# IExceptionHandler (.Net 8.0+)\n\nSince .Net 8.0 there is additional way to handle global exceptions in ASP.NET framework by implementing\none or more IExceptionHandler implementations.\nSee \u003ca href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0#iexceptionhandler\"\u003eMS Docs\u003c/a\u003e.\n\nThis package provides implementation for this approach and it can be used in this way:\n\nCreate class, deriving from `ApiJsonExceptionHandler` (which implements IExceptionHandler interface in it)\n\n```csharp\ninternal sealed class GlobalExceptionHandler : ApiJsonExceptionHandler\n{\n    public GlobalExceptionHandler(ILogger\u003cApiJsonExceptionHandler\u003e logger)\n        : base(logger, new ApiJsonExceptionOptions { ShowStackTrace = true })\n    {\n    }\n\n    // If you want to handle some exceptions more precise...\n    protected override ApiError HandleSpecialException(ApiError apiError, Exception exception)\n    {\n        if (exception is AccessViolationException securityException)\n        {\n            apiError.Status = 401; // or 403\n            apiError.ErrorType = ApiErrorType.AccessRestrictedError;\n        }\n\n        if (exception is SampleDatabaseException dbException)\n        {\n            apiError.Status = 500;\n            if (dbException.ErrorType == DatabaseProblemType.WrongSyntax)\n            {\n                apiError.ErrorType = ApiErrorType.StorageError;\n            }\n        }\n    }\n}\n```\n\nThen register this class with dependency injection container in program.cs\n\n```csharp\nbuilder.Services.AddExceptionHandler\u003cGlobalExceptionHandler\u003e();\n```\n\nand register middleware:\n\n```csharp\napp.UseExceptionHandler(_ =\u003e { });\n```\n\nOptions and Error behaviour is to be handled the same way as described above for Error handling middleware.\n\n#### That's basically it. Happy error handling!\n","funding_links":["https://www.buymeacoffee.com/salixzs"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalixzs%2Faspnetcore.jsonexceptionhandler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalixzs%2Faspnetcore.jsonexceptionhandler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalixzs%2Faspnetcore.jsonexceptionhandler/lists"}