Catching Exceptions in a RESTful API - c#

Should catching exceptions be part of the business logic such as the Service layer, or should they be caught in the controllers' methods?
For example:
Controller UpdateUser method
[HttpPut]
[Route("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> UpdateUserInfo(int id, UserDto userRequest)
{
try
{
var user = _userMapper.ConvertToEntity(userRequest);
var updatedUser = await _userService.UpdateAsync(user, id);
var result = _userMapper.ConvertToUserDto(updatedUser);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError("Exception caught attempting to update user - Type: {ex}", ex.GetType());
_logger.LogError("Message: {ex}", ex.Message);
return StatusCode(500, ex.Message);
}
}
The Service Layer
public async Task<User> UpdateAsync(User user, int id)
{
await _repository.UpdateAsync(user, id);
return user;
}
So, should the exceptions be caught in the service layer or the controller? Or is it subjective?

It's dependent on the business of your application. maybe in your service you should use a try/catch block to adding a log or do anything when exception occurred. but usually I use a global try/catch in a middleware to get exception and send correct response to the client.
public class AdvancedExceptionHandler
{
private readonly RequestDelegate _next;
private readonly ILogger<AdvancedExceptionHandler> _logger;
private readonly IWebHostEnvironment _env;
public AdvancedExceptionHandler(RequestDelegate next, ILogger<AdvancedExceptionHandler> logger, IWebHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task Invoke(HttpContext context)
{
string message = null;
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
if (_env.IsDevelopment())
{
var dic = new Dictionary<string, string>
{
["StackTrace"] = ex.StackTrace,
["Exception"] = ex.Message
};
message = JsonConvert.SerializeObject(dic);
}
else
{
message = "an error has occurred";
}
await WriteToReponseAsync();
}
async Task WriteToReponseAsync()
{
if (context.Response.HasStarted)
throw new InvalidOperationException("The response has already started");
var exceptionResult = new ExceptionResult(message, (int)httpStatusCode);
var result = JsonConvert.SerializeObject(exceptionResult);
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}
}
}
ExceptionResutl class:
public class ExceptionResult
{
public ExceptionResult(string message, int statusCode)
{
this.Message = message;
this.StatusCode = statusCode;
}
public string Message { get; set; }
public int StatusCode { get; set; }
}
public static class ExceptionHandlerMiddlewareExtension
{
public static void UseAdvancedExceptionHandler(this IApplicationBuilder app)
{
app.UseMiddleware<AdvancedExceptionHandler>();
}
}
Then adding middleware in Configure method
public void Configure(IApplicationBuilder app)
{
app.UseAdvancedExceptionHandler();//<--NOTE THIS
}
I don't use try/catch block in controllers. (my opinion)

Catching exceptions in your controller will quickly start to violate some clean code principles, like DRY.
If I understand correctly, the example you have written is that you want to log some errors in case any exceptions are thrown in your code. This is reasonable, but if you begin to add more endpoints, you'll notice you have the same try/catch in all your controller methods. The best way to refactor this is to use a middleware that will catch the exception and map it to a response that you want.
Over time as you begin to update your application to have more features you may have a situation where multiple endpoints are throwing similar errors and you want it to be handled in a similar way. For example, in your example, if the user doesn't exist, the application (in your service layer) may throw an UserNotFoundException, and you may have some other endpoints which can throw the same error, too.
You could create another middleware to handle this or even extend your existing middleware.
One of the better approaches I've seen over the years is to use this library https://github.com/khellang/Middleware/tree/master/src/ProblemDetails to handle the boiler plate for you.

Related

Is it possible to intercept errors thrown by middleware after the controller action is executed?

I'm calling UseExceptionHandler in order to handle errors. This works fine, but not for errors thrown by other (subsequently registered) middleware.
The middleware which exceptions I need to handle is TransactionMiddleware. What it does is to save any changes to the database after a successfully completed call to an action. To be clear - it doesn't just commit a transaction, but also runs all the insert:s/update:s etc. This might fail, for example due to database constraints. (There are also other reasons not to complete the transaction, but they are not included here. Just mentioning that to explain that making the database calls earlier and reducing the TransactionMiddleware to simply commiting won't do the trick.)
Is there a way to NOT start the response before this middleware has run its full course?
My Program.cs
var builder = WebApplication.CreateBuilder(args);
new Startup(builder.Configuration).ConfigureServices(builder.Services);
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// await ExcludedCode()...
});
});
app.UseSwagger()
.UseSwaggerUI();
app.UseRouting()
.UseCors()
.UseAuthentication()
.UseAuthorization()
.UseMiddleware<LanguageMiddleWare>()
.UseMiddleware<TransactionMiddleWare>()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
My (simplified) TransactionMiddleWare-class
public class TransactionMiddleWare
{
private readonly RequestDelegate next;
public TransactionMiddleWare(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context, IDataContext dataContext)
{
try
{
await next(context);
}
catch (PartialExecutionException)
{
this.Commit(context, dataContext);
throw;
}
this.Commit(context, dataContext);
}
private void Commit(HttpContext context, IDataContext dataContext)
{
if (this.ShouldTransactionCommited(context))
dataContext.SaveChanges();
else
throw new Exception("Exception example for clarity.");
}
private bool ShouldTransactionBeCommited(HttpContext context)
{
return true; // Actual code omitted for brevity.
}
}
Example of how my controllers return data (no special stuff):
[ApiController]
[Route("advertisment")]
public class AdvertismentController : ControllerBase
{
private readonly IAdvertismentService advertismentService;
private NLog.ILogger log;
public AdvertismentController(
IAdvertismentService advertismentService)
{
this.log = LogManager.GetCurrentClassLogger();
this.advertismentService = advertismentService;
}
[HttpPost]
public Result<Guid> Create([FromForm] CreateAdvertismentMultipartFormModel request)
{
var id = this.advertismentService.Create(request);
return new Result<Guid> { Data = id };
}
}
Here is what I ended up with. A change in the TransactionMiddleWare class:
public async Task Invoke(HttpContext context, IDataContext dataContext)
{
context.Response.OnStarting(state => {
this.Commit(context, dataContext);
return Task.CompletedTask;
}, context);
try
{
await next(context);
this.Commit(context, dataContext);
}
catch (PartialExecutionException)
{
this.Commit(context, dataContext);
throw;
}
}
That way it will be run and any exception will occur while it's still possible to modify the output and produce an error message.

How to disable Serilog to log one exception?

(Asp.Net core 3.1/.Net 6)
I need to disable the ERR logging of exception TaskCanceledException in an Asp.Net application (built by others). I've used the following middleware to suppress the TaskCanceledException
app.UseMiddleware<TaskCanceledMiddleware>();
public class TaskCanceledMiddleware
{
private readonly RequestDelegate _next;
public TaskCanceledMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (TaskCanceledException)
{
// Set StatusCode 499 Client Closed Request
logger.WARN("...")
context.Response.StatusCode = 499;
}
}
}
I can see the WARN log in the logs. However, I can still find the following EROR messages by Serilog.AspNetCore.RequestLoggingMiddleware? (Note the error level is EROR)
2022-07-06 07:30:40.6636|116344477|EROR|Serilog.AspNetCore.RequestLoggingMiddleware|HTTP "GET" "/api/v1/myurl" responded 500 in 23213.3233 ms
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at ....
Why there is still errors of TaskCanceledException after using app.UseMiddleware<TaskCanceledMiddleware>()? (BTW, what's the level of EROR? Shouldn't it be ERR?)
you can try excluding logging for certain exception like this.
new LoggerConfiguration()
....
.Filter.ByExcluding(logEvent => logEvent.Exception != null && logEvent.Exception.GetType() == typeof(TaskCanceledException))
...CreateLogger();
do you know where the exception is thrown?
Maybe it throws not inside asp.net core action execution pipeline (where Middleware works).
You can register a filter to intercept application wide exceptions inside .AddControllers() registration like this:
_ = services
.AddControllers(options =>
{
//Global filters
_ = options.Filters.Add<ApiGlobalExceptionFilterAttribute>();
///...omissis...
})
Here a simple exception filter attribute implementation:
public sealed class ApiGlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ILogger<ApiGlobalExceptionFilterAttribute> _logger;
public ApiGlobalExceptionFilterAttribute(ILogger<ApiGlobalExceptionFilterAttribute> logger)
{
_logger = logger;
}
public override void OnException(ExceptionContext context)
{
var request = context.HttpContext?.Request;
if (context.Exception is TaskCanceledException tcExc)
{
// Set StatusCode 499 Client Closed Request
_logger.WARN("...");
context.Result = new ErrorActionResult(499);
}
else
{
//TODO: manage errors
}
context.ExceptionHandled = true;
base.OnException(context);
}
}
internal class ErrorActionResult : IActionResult
{
[JsonIgnore]
public int StatusCode { get; private set; }
public ErrorActionResult(int statusCode)
{
StatusCode = statusCode;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var error = new
{
Code = StatusCode,
Message = "Internal server error"
};
var objectResult = new ObjectResult(error)
{
StatusCode = StatusCode
};
await objectResult.ExecuteResultAsync(context);
}
}
The context.ExceptionHandled = true; stops exception to be propagated inside pipeline.
Hope it helps!

How to Pass a GUID to ExceptionFilter for Each Error that Occurs?

I have an API as follows(Mock API), where I am pushing the exception in the catch block to a Central logging Portal. Again in the Exception Filter I am phrasing the Exception Message & throwing the message to User.
[customeFilter]
Public asyc Task<IactionResult> DoTask()
{
try
{
//sample Code
}
catch(exception ex)
{
_log.LogError(ex);
}
public class customeFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
HttpStatusCode? httpErrorCode = HttpStatusCode.BadRequest;
if (context.Exception is DivideByZeroException dvd)
{
context.Result = new ObjectResult(dvd.Data)
{
StatusCode = (int?)HttpStatusCode.InternalServerError,
Value = "There is an issue with the data"
};
context.ExceptionHandled = true;
}
}
}
Issue: I want to keep the same Guid to both systems, i.e., to the central Logging & to the User via Exception Filter. I tried some approach to pass the information to Exception Filter, but didn't have any success with that. Even tried to find any unique identifier in the Exception object, but didn't find any.
Note:
I have a thought about Pushing the exception Log in the Exception Filter instead of Catch block. But I don't want to go with Static Class/Methods.
Do you want to track your user and his/her corresponding exception?
If you wan't my opinion, you don't need to generate a guid for this goal. You can use _httpContextAccessor.HttpContext.TraceIdentifier
Just inject your IHttpContextAccessor inside the filter and use TraceIdentifier.
For more clarification you can set that trace identifier in header of response you can do something like this:
public class CustomFilterAttribute : ExceptionFilterAttribute
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomFilterAttribute(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public override void OnException(ExceptionContext context)
{
HttpStatusCode? httpErrorCode = HttpStatusCode.BadRequest;
if (context.Exception?.InnerException is DivideByZeroException dvd)
{
string uid = _httpContextAccessor.HttpContext.TraceIdentifier;
//logger.log(uid + exception).
_httpContextAccessor.HttpContext.Response.Headers.Add("tracking-uid", uid);
context.Result = new ObjectResult(dvd.Data)
{
StatusCode = (int?)HttpStatusCode.InternalServerError,
Value = "There is an issue with the data"
};
context.ExceptionHandled = true;
}
}
}

c# Exception lost innerException in middleware

I want to be in control of the details of all Exceptions that occured in my web application. I want to add custom data to the exception. I also want to add more info if in debug mode. I do want to pass this as a JSON format to the user.
To do this, I want to throw an exception with an custom error code, and pass the innerexception for debug purposes.
In my startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware(typeof(ErrorHandlingMiddleware)); // Error handling middlware
....
In my service I throw an Exception:
catch (Exception e)
{
throw new Exception("E18", e.InnerException);
}
When I debug this. I can see e.InnerException is filled with data.
Here comes the magic... well... sort of. This is the middleware:
public class ErrorHandlingMiddleware
{
static Dictionary<string, APIMessageDetails> responseMessageCodes = new Dictionary<string, APIMessageDetails>
{
...
{"E18", new APIMessageDetails {responseMessage = "An unknown error occured.", httpStatusCode = StatusCodes.Status500InternalServerError}},
...
}
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
string errorCode = ex.Message;
APIMessageDetails result;
APIMessage apiMessage = new APIMessage();
if (errorCode != null)
{
if (responseMessageCodes.TryGetValue(errorCode, out result))
{
apiMessage.responseMessageCode = errorCode;
apiMessage.messageDetails = result;
#if DEBUG
apiMessage.messageDetails.exception = ex;
#endif
}
}
var jsonResult = JsonConvert.SerializeObject(apiMessage);
context.Response.ContentType = "application/json";
context.Response.StatusCode = apiMessage.messageDetails.httpStatusCode;
return context.Response.WriteAsync(jsonResult);
}
}
When I debug, I can see that the exception that is catched in the middleware, does contain the E18, but the innerException is null. I do not understand why that is; it is passed to the Exception that is thrown...
I hope someone could help me out here.
Oeps... seems like everything was okay whit the code above. Seems there is some middleware that causes double requests...
Have to figure that out, but by disabling the middleware the innerException has been filled.
Thanks all.

ASP.NET Core equivalent of ASP.NET MVC 5's HttpException

In ASP.NET MVC 5 you could throw a HttpException with a HTTP code and this would set the response like so:
throw new HttpException((int)HttpStatusCode.BadRequest, "Bad Request.");
HttpException does not exist in ASP.NET Core. What is the equivalent code?
I implemented my own HttpException and supporting middleware which catches all HttpException's and turns them into the corresponding error response. A short extract can be seen below. You can also use the Boxed.AspNetCore Nuget package.
Usage Example in Startup.cs
public void Configure(IApplicationBuilder application)
{
application.UseIISPlatformHandler();
application.UseStatusCodePagesWithReExecute("/error/{0}");
application.UseHttpException();
application.UseMvc();
}
Extension Method
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseHttpException(this IApplicationBuilder application)
{
return application.UseMiddleware<HttpExceptionMiddleware>();
}
}
Middleware
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException httpException)
{
context.Response.StatusCode = httpException.StatusCode;
var responseFeature = context.Features.Get<IHttpResponseFeature>();
responseFeature.ReasonPhrase = httpException.Message;
}
}
}
HttpException
public class HttpException : Exception
{
private readonly int httpStatusCode;
public HttpException(int httpStatusCode)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message) : base(message)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message) : base(message)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = (int)httpStatusCode;
}
public int StatusCode { get { return this.httpStatusCode; } }
}
In the long term, I would advise against using exceptions for returning errors. Exceptions are slower than just returning an error from a method.
After a brief chat with #davidfowl, it seems that ASP.NET 5 has no such notion of HttpException or HttpResponseException that "magically" turn to response messages.
What you can do, is hook into the ASP.NET 5 pipeline via MiddleWare, and create one that handles the exceptions for you.
Here is an example from the source code of their error handler middleware which will set the response status code to 500 in case of an exception further up the pipeline:
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ErrorHandlerOptions _options;
private readonly ILogger _logger;
public ErrorHandlerMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
ErrorHandlerOptions options)
{
_next = next;
_options = options;
_logger = loggerFactory.CreateLogger<ErrorHandlerMiddleware>();
if (_options.ErrorHandler == null)
{
_options.ErrorHandler = _next;
}
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError("An unhandled exception has occurred: " + ex.Message, ex);
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started,
the error handler will not be executed.");
throw;
}
PathString originalPath = context.Request.Path;
if (_options.ErrorHandlingPath.HasValue)
{
context.Request.Path = _options.ErrorHandlingPath;
}
try
{
var errorHandlerFeature = new ErrorHandlerFeature()
{
Error = ex,
};
context.SetFeature<IErrorHandlerFeature>(errorHandlerFeature);
context.Response.StatusCode = 500;
context.Response.Headers.Clear();
await _options.ErrorHandler(context);
return;
}
catch (Exception ex2)
{
_logger.LogError("An exception was thrown attempting
to execute the error handler.", ex2);
}
finally
{
context.Request.Path = originalPath;
}
throw; // Re-throw the original if we couldn't handle it
}
}
}
And you need to register it with StartUp.cs:
public class Startup
{
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerfactory)
{
app.UseMiddleWare<ExceptionHandlerMiddleware>();
}
}
Alternatively, if you just want to return an arbitrary status code and aren't concerned with the Exception-based approach, you can use
return new HttpStatusCodeResult(400);
Update: as of .NET Core RC 2, the Http prefix is dropped. It is now:
return new StatusCodeResult(400);
The Microsoft.AspNet.Mvc.Controller base class exposes a HttpBadRequest(string) overload which takes an error message to return to the client. So from within a controller action, you could call:
return HttpBadRequest("Bad Request.");
Ultimately my nose says any private methods called from within a controller action should either be fully http-context-aware and return an IActionResult, or perform some other small task completely isolated from the fact that it's inside of an http pipeline. Granted this is my personal opinion, but a class that performs some piece of business logic should not be returning HTTP status codes, and instead should be throwing its own exceptions which can be caught and translated at the controller/action level.
There is no equivalent in ASP.NET Core itself. As others have said, the way to implement this is with a middleware and your own exceptions.
The Opw.HttpExceptions.AspNetCore NuGet package does exactly this.
Middleware and extensions for returning exceptions over HTTP, e.g. as ASP.NET Core Problem Details. Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://www.rfc-editor.org/rfc/rfc7807. But you are not limited to returning exception results as Problem Details, but you can create your own mappers for your own custom formats.
It is configurable and well documented.
Here is the list of provided exceptions out of the box:
4xx
400 BadRequestException
400 InvalidModelException
400 ValidationErrorException<T>
400 InvalidFileException
401 UnauthorizedException
403 ForbiddenException
404 NotFoundException
404 NotFoundException<T>
409 ConflictException
409 ProtectedException
415 UnsupportedMediaTypeException
5xx
500 InternalServerErrorException
500 DbErrorException
500 SerializationErrorException
503 ServiceUnavailableException
Here is an extended version of #muhammad-rehan-saeed answer.
It logs exceptions conditionaly and disables http cache.
If you use this and UseDeveloperExceptionPage, you should call UseDeveloperExceptionPage before this.
Startup.cs:
app.UseMiddleware<HttpExceptionMiddleware>();
HttpExceptionMiddleware.cs
/**
* Error handling: throw HTTPException(s) in business logic, generate correct response with correct httpStatusCode + short error messages.
* If the exception is a server error (status 5XX), this exception is logged.
*/
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException e)
{
var response = context.Response;
if (response.HasStarted)
{
throw;
}
int statusCode = (int) e.StatusCode;
if (statusCode >= 500 && statusCode <= 599)
{
logger.LogError(e, "Server exception");
}
response.Clear();
response.StatusCode = statusCode;
response.ContentType = "application/json; charset=utf-8";
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
var bodyObj = new {
Message = e.BaseMessage,
Status = e.StatusCode.ToString()
};
var body = JsonSerializer.Serialize(bodyObj);
await context.Response.WriteAsync(body);
}
}
}
HTTPException.cs
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }
public HttpException(HttpStatusCode statusCode)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode)
: this((HttpStatusCode) httpStatusCode)
{
}
public HttpException(HttpStatusCode statusCode, string message)
: base(message)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode, string message)
: this((HttpStatusCode) httpStatusCode, message)
{
}
public HttpException(HttpStatusCode statusCode, string message, Exception inner)
: base(message, inner)
{
}
public HttpException(int httpStatusCode, string message, Exception inner)
: this((HttpStatusCode) httpStatusCode, message, inner)
{
}
}
I had better results with this code than with :
UseExceptionHandler:
automatically logs every "normal" exceptions (ex 404).
disabled in dev mode (when app.UseDeveloperExceptionPage is called)
cannot catch only specific exceptions
Opw.HttpExceptions.AspNetCore: logs exception when everything works fine
See also ASP.NET Core Web API exception handling
Starting from ASP.NET Core 3 you can use ActionResult to return HTTP status code:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ITEMS_TYPE> GetByItemId(int id)
{
...
if (result == null)
{
return NotFound();
}
return Ok(result);
}
More details are here: https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1

Categories