How to add value to the default exception response in ABP? - c#

I want to add an ID (GUID) to the exception and:
Log it
Return it to the client json response
Where should I generate this log ID value and add it to the exception message that is logged. And where to change the following default response?
{
"targetUrl": null,
"result": null,
"success": false,
"error": {
"message": "An internal error occurred during your request!",
"details": "..."
},
"unAuthorizedRequest": false
}
I am using .NET Core version.

If you want to disable displaying the message for a particular AJAX call, add abpHandleError: false into the abp.ajax options.
Or you can disable the default behavior of the framework exception wrapper
public class PeopleController : AbpController
{
[HttpPost]
[WrapResult(WrapOnSuccess = false, WrapOnError = false)]
public JsonResult SavePerson(SavePersonModel person)
{
//TODO: save new person to database and return new person's id
return Json(new {PersonId = 42});
}
}
https://aspnetboilerplate.com/Pages/Documents/Javascript-API/AJAX?searchKey=wrap#asp-net-mvc-controllers
Another thing is; you can send exception details to the client by the below configuration
...
using Abp.Web.Configuration;
...
public override void PreInitialize()
{
Configuration.Modules.AbpWebCommon().SendAllExceptionsToClients = true;
}
...
https://aspnetboilerplate.com/Pages/Startup-Configuration#configuring-modules
Result Wrapping & Exception Handling:
ASP.NET Boilerplate does not wrap Web API actions by default if an action has successfully executed. It, however, handles and wraps exceptions. You can add the WrapResult/DontWrapResult attributes to actions and controllers for finer control. You can change this default behavior from the startup configuration (using Configuration.Modules.AbpWebApi()...). See the AJAX document for more info about result wrapping.
https://aspnetboilerplate.com/Pages/Documents/Web-API-Controllers?searchKey=wrap#result-wrapping-exception-handling
Wrapping Results
ASP.NET Boilerplate wraps the return values of dynamic Web API actions using an AjaxResponse object. See the ajax documentation for more information on wrapping. You can enable/disable wrapping per method or per application service. See this example application service:
public interface ITestAppService : IApplicationService
{
[DontWrapResult]
DoItOutput DoIt(DoItInput input);
}
https://aspnetboilerplate.com/Pages/Documents/Dynamic-Web-API?searchKey=wrap#wrapping-results
Lastly you can write your own ResultWrapperHandler...
public class CustomResultWrapperHandler : ResultWrapperHandler, ITransientDependency
{
//...
protected override void WrapResultIfNeeded(HttpRequestMessage request, HttpResponseMessage response)
{
//...
base.WrapResultIfNeeded(request, response);
}
}

if you want to get special message in some case you can use
throw new UserFriendlyException("your message");
the above code just effects on error message and doesn't show the details.
so its good option for production version.

Related

How to catch bad URL token eror when calling .NET 5 ASP.NET MVC REST API C# is called? Middleware does not catch the exception

How to catch this error in the back-end C# code?
See the solution usedat the end of the question description.
It is due to integer overflow for a HTTP GET URL where the URL contains an integer parameter. Calls with a 32 bit integer sized value work (e.g., "1234" works and the API is called).
HTTP GET www.domain.net/api/product/{productIdInteger}
Where the JavaScript caller calls HTTPS
www.domain.net/api/product/222222222222222222222222222222222222
The response looks like:
{
"errors": {
"productIdInteger": [
"The value '222222222222222222222222222222222222' is not valid."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-f6a8a724853f3f44ba9fc6468a42ad94-aca067537e73b645-00"
}
Startup.cs adds a middleware to catch exceptions at the WebAPI controller level.
This does not catch the URL parameter parsing error or a bad JSON in HTTP Body parsing error
Startup.cs has app.UseMiddleware();
MyErrorMiddleware has this invoke method and MyExceptionHandlerAsync is never called for this case.
public async Task Invoke(HttpContext context)
{
try
{
//This line gets called
await _next(context);
}
catch (Exception ex)
{
//The catch block is not executed if the integer passed to the controller method overflows a 32 bit integer
//The catch block is executed if an C# WebAPI controller method is called and the controller method throws an exception
await MyExceptionHandlerAsync(context, ex);
}
}
Here is what worked best for our application.
Create base class for controller methods (code below) having a Boolean errorFlag and a List ErrorList
Derive the return objects for controller methods off the base class in #1. Each of the return classes had the error properties plus the data returned from the controller method. The data property returned for the controller would be called "ReturnData" to make the front-end JavaScript easier.
Add the model validation error handler to Startup.cs. Return the same base class as in step #1
Add in the error handling middleware catch block, code to return the error using the same error class from step #1
This handles 1) business error from controller (not a thrown exception), 2) exception thrown from controller or a method it calls, no need to have a try/catch in the controller, 3) error passing data to the WebAPI controller method (model validation error)
public class ReturnBaseClass
{
public ReturnBaseClass() { errorList = new List<string>()}
public bool errorFlag {get;set;}
public List<string> errorList {get;set;}
}
//A controller return data could look like
public class CourseDataReturn : ReturnBaseClass
{
//Always called ReturnData to make front-end JavaScript easier
public CourseInfo ReturnData {get;set;}
}
//CourseInfo is the data returned from the data repository method called from the controller.
public class CourseInfo
{
public int CourseId {get;set;}
public string CourseName {get;set;}
}
We simplified our return HTTP status codes to OK, Bad Request, Internal Server Error for back-end WebAPI calls which are made. All other HTTP status codes would be a HTTP transport error (timeout, not found (URL is bad), ....
That got us past the REST mismatch of HTTP NotFound being either the URL is not found, such as domain name does not exist, and the database row being retrieved for a HTTP GET student by student ID not finding the student.
It is not an exception. It is ModelState not valid.
So you can catch this error by:
Step 1:
Add ConfigureApiBehaviorOptions in Program.cs file:
builder.Services.AddControllers().ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
});
Step 2:
Handle errors in top of Controller file:
if (!ModelState.IsValid) {
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
// Do something to handle error list
}
The code below is based on [https://briancaos.wordpress.com/2019/11/29/net-core-catch-model-binding-exceptions/]
We used a variation on this code in the solution
//In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
... //all of the configuration for services, etc...
//Add this at the very end of ConfigureServices()
//Handle errors - model bind error, bad parameter passed to WebAPI endpoint, ...
services.Configure<ApiBehaviorOptions>(options =>
options.InvalidmsResponseFactory = (ac =>
{
var ms = ac.ms;
//Properties related to errors: ms.IsValid, s.ErrorCount, ms.ValidationState
//var errors = ms.Where(x => x.Value.Errors.Count > 0);
var errs = new ValidationProblemDetails(ac.ms);
var errList = new List<string>();
//errList.Add($"Detail: {errs.Detail}");
errList.Add($"Title: {errs.Title}");
//errList.Add($"URL: {errs.Instance}");
//errList.Add($"HTTP Status Code: {errs.Status}");
//errList.Add($"Error Type: {errs.Type}");
List<string> mlist = new List<string>();
foreach (string k in errs.Errors.Keys)
{
//build one message line <KEY>: <ERROR MESSAGE 1>, <ERROR MESSAGE 2>, ...
string m = $"{k}: ";
string[] value = errs.Errors[k];
if (value.Length > 0)
{
m = $"{m}{string.Join(",", value)}";
}
mlist.Add(m);
}
string r = "";
if (msgList.Any())
{
r = string.Join(",", msgList);
}
return new BadRequestObjectResult(r);
}
));

ASP.NET Core 6 Web API - returns all errors as JSON

ASP.NET Core returns (if error) only HTTP status code and empty body. Is it possible to handle all errors?
I would like return HTTP status code and json? For example
{
"status": STATUS_CODE,
"message": "error"
}
You can use filters for this. Create a filter similar to this:
public class HandledResultFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
context.ExceptionHandled = true;
var error = new HandledResult<Exception>(context.Exception).HandleException();
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
context.HttpContext.Response.StatusCode = error.StatusCode;
context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(error), Encoding.UTF8);
}
}
And register it:
services.AddMvc(mvcOptions =>
{
mvcOptions.Filters.Add(new HandledResultFilter());
});
You can create your own middleware to handle any errors and return to the client any information you want.
You can place the middleware in the Startup. Configure method before any other middleware and before controller endpoint mappings to handle every error.
Details are in this article: Error Handler Tutorial for ASP NET

Inconsistent error response in .NET 6 Web API application

I am not able to find how to throw exceptions as they are generated by .NET 6 Web API.
If I return BadRequest(ModelState) with added errors I am not getting same message with status, type, title etc.
By default .NET generates this kind of errors when validation error occurs:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-488f8c0057223cbba92fc1fbfc8865d8-2341d7aba29d098f-00",
"errors": {
"$": [
"The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 7 | BytePositionInLine: 0."
],
"model": [
"The model field is required."
]
}
}
I want to configure my application to respond with the same error JSON, or I want to configure so it will respond with the custom JSON fields.
I tried to add a middleware that will catch exceptions, but it does not handle model errors (which are handled by framework by itself). How can I handle errors globally, or how can I throw exceptions that will be treated the same as framework handles them? Any documentation/tutorial links are welcome!
You can disable default bad request responses like the following code:
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
So you can return any model you want in BadRequest.
However, you then have to do the model validation yourself in each endpoint like:
[HttpPost(Name = "Post1")]
public IActionResult Post1(Model1 model)
{
if (!ModelState.IsValid)
return BadRequest(new CustomApiResponse());
...
}
[HttpPost(Name = "Post2")]
public IActionResult Post2(Model2 model)
{
if (!ModelState.IsValid)
return BadRequest(new CustomApiResponse());
...
}
If you want to return a global JSON structure, you can create a filter with the ActionFilterAttribute and use it for all your endpoints so you don't need to do model validation on every endpoint.
Custom Validation Filter:
public class CustomValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(new GlobalApiResponse());
}
base.OnActionExecuting(context);
}
}
You need to register your custom filter in Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add(typeof(CustomValidationFilterAttribute));
});
This is just one of the ways you can use to achieve what you want, you can find different methods on the internet.
In .Net6 Web Api, If You Want To Validate Model By Your Code, And You Can Debug It, Just Do This:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(new BaseResponse() { code = ResponseCode.失败, msg = "" });
}
return base.OnActionExecutionAsync(context, next);
}
}
Then Do It In The Program.cs
// Add services to the container.
builder.Services.AddControllers(options =>
{
//Add Your Filter
options.Filters.Add<ModelValidationFilterAttribute>();
}).ConfigureApiBehaviorOptions(options =>
{
//Disable The Default
options.SuppressModelStateInvalidFilter = true;
});

Custom error handling globally in asp.net web api

I am using asp.net web api.
[Route("api/employee")]
[HttpPost]
public dynamic GetData(EmployeeModel model)
{
EmployeeService emp = new EmployeeService();
emp.GetData(model);
}
This is how I am handling error globally:
public class ExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
//Code to log the exception goes here:
}
}
in the WebApiConfig.cs file, I am registering the filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
....
config.Filters.Add(new ExceptionFilter());
}
}
Whenever there is an exception, the exception filter is able to catch the exception & It can log the exception. All this is working fine.
What I want: With every exception, I want to log specific details, those details are available in respective methods but how do I pass them to exception filter? The only work around I see is, add try catch block in individual methods and log the exception along with specific details.
Another thing that I want to capture is the request object of each request. In this case it will be ExployeeModel. Even if I somehow get the request object, how to I type cast into correct type. One endpoint can expect EmployeeModel other can expect DepartmentModel.

Error Handling for ASP.NET Odata Web API

I'm interested in knowing what are the best practices being followed to raise exceptions in the ODataController.
If you raise an exception in the method it is translated to response code of 500 by default and the content has details on the error. I would like to be explicit of the response code and send 400 in cases of invalid key.
For example: If the input request has an invalid key would like to return a HttpResponseCode of 400 and content should have the error details similar to raising an exception.
Thanks a lot for your inputs
OData (at least since v3) uses specific json to represent errors:
{
"error": {
"code": "A custom error code",
"message": {
"lang": "en-us",
"value": "A custom long message for the user."
},
"innererror": {
"trace": [...],
"context": {...}
}
}
}
Microsoft .Net contains Microsoft.Data.OData.ODataError and Microsoft.Data.OData.ODataInnerError classes to form OData error on a server side.
To form proper OData error response (HttpResponseMessage), that contains error details you can:
1) form and return HttpResponseMessage in controller's action using System.Web.OData.Extensions.HttpRequestMessageExtensions.CreateErrorResponse method
return Request.CreateErrorResponse(HttpStatusCode.Conflict, new ODataError { ErrorCode="...", Message="...", MessageLanguage="..." }));
2) throw HttpResponseException using the same method for creating HttpResponseMessage
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, new ODataError { ErrorCode="...", Message="...", MessageLanguage="..." }));
3) throw custom typed exception and convert it using Web Api action filters
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is CustomException)
{
var e = (CustomException)context.Exception;
var response = context.Request.CreateErrorResponse(e.StatusCode, new ODataError
{
ErrorCode = e.StatusCodeString,
Message = e.Message,
MessageLanguage = e.MessageLanguage
});
context.Response = response;
}
else
base.OnException(context);
}
}
Use HttpResponseException,
e.g. throw new HttpResponseException(HttpStatusCode.NotFound);.
Details can be found here.
For ASP.NET Core with OData, replace the EnableQueryAttribute on your Get method with a custom attribute that catches ODataException and throws a custom exception. In most cases, this allows standard error handling to step in as expected. Originally found this solution at https://github.com/OData/WebApi/issues/1898.
For your custom attribute, use something like the following:
public class CustomEnableQueryAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)
{
try
{
base.ValidateQuery(request, queryOptions);
}
catch (ODataException e)
{
throw new CustomException(e.Message, e){UserMessage = "Invalid OData query."};
}
}
}
On your Get method, use something like the following:
[HttpGet, CustomEnableQuery]
public virtual IQueryable<TDomainDto> Get()
{
return Repository.Get();
}
Check CreateErrorResponse in the OData documentation. The namespace to the used in Microsoft.AspNet.OData. I got my code working using this.

Categories

Resources