DataException from Dapper disappear when using MediatR - c#

I have a strange problem with DataExceptions from Dapper doesn't properagte correctly.
Here's my setup:
public class CustomerController : Controller
{
private readonly IMediator _mediator;
public CustomerController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> Get(Get.Query query)
{
var result = await _mediator.Send(query);
return Ok(result);
}
}
public class Get
{
public class Query : IRequest<IEnumerable<Result>>
{
}
public class Result
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class QueryHandler : IAsyncRequestHandler<Query, IEnumerable<Result>>
{
private readonly IDbConnection _dapper;
public QueryHandler(IDbConnection dapper)
{
_dapper = dapper;
}
public async Task<IEnumerable<Result>> Handle(Query message)
{
// the below throws because of incorrect type mapping
// (yes, the connection is open)
var customers =
await _dapper.Connection.QueryAsync<Result>("SELECT Id, Name FROM [Customer].[Customers]");
return customers;
}
}
}
The result
Curl
curl -X GET 'http://localhost:5000/api/Customer'
Request URL
http://localhost/api/Customer
Response Body
no content
Response Code
500
Expected
I was expecting 500 with an error description and not no content.
This is the exception thrown:
If I change my Handle method to:
public async Task<IEnumerable<Result>> Handle(Query message)
{
throw new DataException("What is going on?");
}
I get the expected result. A 500 with an error saying "What is going on?"
Because I have app.UseDeveloperExceptionPage(); enabled it looks like this.
An unhandled exception occurred while processing the request.
DataException: What is going on?
...Customer.Get+QueryHandler+<Handle>d__2.MoveNext() in Get.cs, line 42
Stack Query Cookies Headers
DataException: What is going on?
...
But that's expected.
So what is going on? Why doesn't the DataException from Dapper work as expected?

MediatR's preprocessor aggreggates all exception from pipeline in AggregateException.
You have to expose it - for instance with ExceptionFilterAttribute:
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
Exception exception = context.Exception;
if (exception is AggregateException
&& exception.InnerException != null)
{
exception = exception.InnerException;
}
// check type and do you stuff ........
context.Response = new HttpResponseMessage
{
Content = this.CreateContent(response),
StatusCode = HttpStatusCode.BadRequest
};
//....

Related

Catch/translate exception from ASP.NET OData Get method

I have an OData service implemented, using ASP.NET OData library.
So, I have a controller like this one:
public class ProjectsController : ODataController
{
private readonly MyContext _db;
public ProjectsController(MyContext db)
{
_db = db;
}
[EnableQuery]
public IQueryable<Project> Get(string customQuery)
{
var query = _db.Projects;
if (!string.IsNullOrWhitespace(customQuery))
{
query = query.Where(/* something complex going here */);
}
return query.OrderByDescending(p => p.Id);
}
}
Now, all this works fine. But, in some cases, some specific "customQuery" may produce SQL code which results in division by zero. And, as result of that, the server sends back status 500 (oops) and the error object like this one:
{"error":{"code":"","message":"An error has occurred."}}
This is not very informative. I want to catch the exception and translate it 400 with some meaningful message (advising the user how to fix the custom query).
I've tried setting global exception filter, attribute exception filter.. no luck. Any ideas?
In case anyone is interested the way to go was to implement my own IExceptionHandler:
class MyExceptionHandler : IExceptionHandler
{
public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
if (context.Request.Method == HttpMethod.Get && context.Request.RequestUri.AbsolutePath == "/odata/projects")
{
if (IsDivideByZero(context.Exception))
{
const string message = "Division by zero encountered while applying the filter";
var response = context.Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError(message));
context.Result = new ResponseMessageResult(response);
}
}
return Task.CompletedTask;
}
private static bool IsDivideByZero(Exception ex)
{
if (ex is SqlException sqlEx && sqlEx.Number == 8134)
return true;
return ex.InnerException != null && IsDivideByZero(ex.InnerException);
}
}
and register it with DI like this:
private static void Configure(HttpConfiguration config)
{
// ...
config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
// ...
}

Exception handler middleware not catching

I'm developping a web API with ASP.NET Core and I'm trying to implement a custom error handling middleware so I can throw standard exceptions that can be converted into a JSON response with the appropriate HTTP Status code.
For example if I do:
throw new NotFoundApiException("The object was not found");
I need it to be converted into:
StatusCode: 404
ContentType: application/json
ResponseBody: {"error": "The object was not found"}
Here is my middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
} catch (ApiException ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, ApiException exception)
{
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.httpStatusCode;
return context.Response.WriteAsync(result);
}
}
Exceptions
public class ApiException : System.Exception
{
private int _httpStatusCode = (int)HttpStatusCode.InternalServerError;
public ApiException() { }
public ApiException(string message): base(message) { }
public int httpStatusCode {
get { return this._httpStatusCode; }
}
}
public class NotFoundApiException : ApiException
{
private int _httpStatusCode = (int)HttpStatusCode.BadRequest;
public NotFoundApiException() { }
public NotFoundApiException(string message): base(message) { }
}
Startup
public void Configure(/*...*/)
{
loggerFactory.AddConsole();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMvc();
}
Controller action
[HttpGet("object/{guid}")]
public WebMessage Get(Guid guid)
{
throw new NotFoundApiException(string.Format("The object {0} was not found", guid));
//...
I can see the request entering my registered middleware but the exception is not catched and simply thrown as usual.
I'm suspecting a race condition or something similar, I don't know very much about them async functions actually.
Has someone got an idea why my exception is not catched ?
edit By continuing the execution with VisualStudio I can see the expected behavior: I'm finally getting my response.
Seems like the Exception is not really catched by the middleware but somehow processed afterwards.
My solution to this problem was to remove app.UseDeveloperExceptionPage(); in Startup.cs
In my case, I found that app.UseMiddleware<ExceptionHandlingMiddleware>(); should be at the top of Configure() method.
You can try also Exception filters.
(of course, filters are not so flexible like as error handling middleware, which is better in general case, but - at least for me - filters are working fine without any issues)
That's what I'm using:
public class ExceptionGlobalFilter : ExceptionFilterAttribute
{
private readonly ILogger logger;
public ExceptionGlobalFilter(ILoggerFactory lf)
{
logger = lf.CreateLogger("ExceptionGlobalFilter");
}
public override void OnException(ExceptionContext context)
{
var customObject = new CustomObject(context.Exception);
//TODO: Add logs
if (context.Exception is BadRequestException)
{
context.Result = new BadRequestObjectResult(customObject);
}
else if (context.Exception is NotFoundException)
{
context.Result = new NotFoundObjectResult(customObject);
}
else
{
context.Result = new OkObjectResult(customObject);
}
base.OnException(context);
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
await base.OnExceptionAsync(context);
return;
}
}
Startup.cs:
services.AddMvc(config =>
{
config.Filters.Add(typeof(ExceptionGlobalFilter));
});
More info:
Introduction to Error Handling in ASP.NET Core
Exception filters
Filters
MVC Issue #5594
ExceptionHandlerMiddleware.cs
In my case app.UseDeveloperExceptionPage(); was written in the Startup after the exception handler middleware. The fix was simply by moving the exception handler middleware to be after it.
#Pierre, I have met the same issue here when using Middleware as the global exception handler. The issue was caused by my mistake to wrote an "async void" method, I have throwed an exception in the method named "NewException":
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async void NewException()
{
throw new InvalidOperationException("WTF");
}
The exception [InvalidOperationException("WTF")] will not be catching by the Middleware, if I change the code snippet to :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
await NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async Task NewException()
{
throw new InvalidOperationException("WTF");
}
The exception Middleware will catch it. Hope this help.

using global exception handeling messes up DelegatingHandler

When ovveride the IExceptionHandler, the response does not reach the DelegatingHandler when a unexpected exception occurs. How can I fix this?
In webapi 2, I want to implement a audit logger for request and response messages. I also want to add a global exception handler. However, when I replace the IExceptionHandler with my custom implementation. the response never reaches the DelegatingHandler -on exception - And thus the audit for response is lost.
in WebApiConfig
// add custom audittrail logger
config.MessageHandlers.Add(new AuditLogHandler());
// replace global exception handeling
config.Services.Replace(typeof(IExceptionHandler), new WebAPiExceptionHandler());
Custom Exception Handler
public class WebAPiExceptionHandler : ExceptionHandler
{
//A basic DTO to return back to the caller with data about the error
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new ErrorInformation { Message = "Iets is misgegaan", ErrorDate = DateTime.UtcNow }));
}
}
Custom Auditlogger
public class AuditLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
var task = await request.Content.ReadAsStringAsync();
// .. code for loggign request
}
var result = await base.SendAsync(request, cancellationToken);
// .. code for logging response
// when I do not replace WebAPiExceptionHandler, code is reachred here
// When I Do use WebAPiExceptionHandler, code is not reached here
return result;
}
}
Code for throwing exception in webapi
public class Values_v2Controller : ApiController
{
public string Get(int id)
{
throw new Exception("haha");
}
}
Dont use ExceptionHandler as base class, implement interface IExceptionHandler
public class WebAPiExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var fout = new ErrorInformation
{
Message = "Iets is misgegaan"
, ErrorDate = DateTime.UtcNow
};
var httpResponse = context.Request.CreateResponse(HttpStatusCode.InternalServerError, fout);
context.Result = new ResponseMessageResult(httpResponse);
return Task.FromResult(0);
}
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
}
The problem is that ExceptionHandler only executes Handle(ExceptionHandlerContext context) method if ShouldHandle(ExceptionHandlerContext context) returns true.
Overriding bool ShouldHandle(ExceptionHandlerContext context) to always return true fix the problem for me.

What is the correct way to do global exception handling with asp.net mvc6 vnext [duplicate]

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

Web Api always returns http status code 200 when an exception occurs

public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse("The current operation could not be completed sucessfully.);
}
}
When a call this Get action:
[HttpGet]
public async Task<IHttpActionResult> Get()
{
Convert.ToInt16("this causes an exception state");
var data = await service.Get();
return Ok(data);
}
An exception is raised... and my global exc handler is triggered.
When my custom response is returned to the client my fiddler always says:
Result: 200
I could also change the return Ok(data); to return NotFound();
That will not change anything in the result status code.
How can I overwrite/intercept the http status creation and return my own status code 500 instead?
On my web client I need to show a nice error dialog with a logging id + error message ONLY when status code 500 is returned.
You need to set the status code on the IHttpActionResult:
public class NiceInternalServerExceptionResponse : IHttpActionResult
{
public string Message { get; private set; }
public HttpStatusCode StatusCode { get; private set; }
public NiceInternalServerExceptionResponse(
string message,
HttpStatusCode code)
{
Message = message;
StatusCode = code;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(StatusCode);
response.Content = new StringContent(Message);
return Task.FromResult(response);
}
}
And in your GlobalExceptionHandler pass HttpStatusCode.InternalServerError (500):
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse(
"The current operation could not be completed sucessfully.",
HttpStatusCode.InternalServerError);
}
I do it like this...
[HttpPost]
public HttpResponseMessage Post()
{
try
{
// Do stuff
}
catch (Exception ex)
{
// Something went wrong - Return Status Internal Server Error
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
Works same for a Get.
You could use next code for custom error:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");

Categories

Resources