.NET Core 3 ExceptionHandler with Generic Logger parameter - AmbiguousMatchException - c#

I am trying to implement .NET Core 3 app.UseExceptionHandler for an API project, but I have a generic logger per API controller that I would like to pass into the error method. If, for instance, the error happens in my WorkingController, I would like to have ILogger<WorkingController> be the logging entity. I have found that using the built-in ExceptionHandler, I lose some of the context of the request, and I would like to capture this context if possible.
Here's what all of my API methods used to look like before:
[Route("api/working")]
[ApiController]
public class WorkingController
{
private readonly ILogger<WorkingController> _logger;
public WorkingController(ILogger<WorkingController> logger)
{
_logger = logger;
}
[Route("workingRoute")]
public IActionResult SomeMethod()
{
_logger.LogInformation("Starting SomeMethod");
try
{
// doing some stuff here
}
catch(Exception ex)
{
_logger.LogError(ex, "Something happened");
return Problem();
}
return Ok();
}
}
I tried setting up a BaseErrorController from which other Controllers could inherit:
[ApiController]
public abstract class BaseErrorController<T> : ControllerBase
{
protected readonly ILogger<T> Logger;
public BaseErrorController(ILogger<T> logger)
{
Logger = logger;
}
[AllowAnonymous]
[Route("/error")]
public IActionResult Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (context != null)
{
var ex = context.Error;
Logger.LogError(ex, $"{context.Path} failed - {ex.Message}");
return Problem(
detail: context.Error.StackTrace,
title: context.Error.Message);
}
return Problem();
}
}
And now my former WorkingController looks like this, which is arguably a lot cleaner (and less code):
[Route("api/working")]
[ApiController]
public class WorkingController : BaseErrorController<WorkingController>
{
public WorkingController(ILogger<WorkingController> logger) : base(logger) { }
[Route("workingRoute")]
public IActionResult SomeMethod()
{
Logger.LogInformation("Starting SomeMethod");
// doing some stuff here
return Ok();
}
}
In Startup.cs, I'm registering this all with app.UseExceptionHandler("/error"), and it seems to work OK . . . except now in my logs, I see the following error (because I have more than one controller implementing this base controller):
An exception was thrown attempting to execute the error handler.
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Matches: Namespace.Controllers.WorkingController.Error (Namespace)
Namespace.Controllers.AnotherController.Error (Namespace)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
Does anybody have an idea what might be the solution here? Is there an overload of the ExceptionHandler that might be what I'm looking for? Is this solution too boutique, and I should go back to what I was doing before? Help me, Stack Overflow. Thank you!

Unfortunately, there is simply no built-in way to do this cleanly. You could find a package to help here (I haven't looked), or you could want to re-write some parts of ASP.NET Core, but I wouldn't really want to do that.
There is another way of doing this, which depending on which version you like more, is recommended/recommended against, but I'm pro of the former. Instead of throwing/catching exceptions at the Controller level, I treat Controllers as the dumbest thing possible, so they just call some service and that's it.
If you want to know where an exception was thrown, or you specifically want the exception to go uncaught, a strategy my team follows is to create custom exceptions. You could then leave these uncaught (and the HTTP500 will be returned to the caller) or you could have a custom Middleware and define there what should happen.
The following is an example, written entirely here so there may be some changes needed, and it's solely to demonstrate a possible approach, not a working demo.
Given some exceptions valid to your domain:
public class UserNotFoundException : Exception { public Guid UserId { get; set; } }
public class InvalidModelException : Exception { }
And an exception handler:
public class MyCustomExceptionHandlerMiddleware
{
private readonly ILogger<MyCustomExceptionHandlerMiddleware> _logger;
public MyCustomExceptionHandlerMiddleware(ILogger<MyCustomExceptionHandlerMiddleware> logger)
{
_logger = logger;
}
public async Task Invoke(RequestDelegate next)
{
try
{
await next(); // without this, the request doesn't execute any further
}
catch (UserNotFoundException userNotFound)
{
_logger.LogError(userNotFound, "The user was not found");
// manipulate the response here, possibly return HTTP404
}
catch (Exception ex)
{
_logger.LogError(ex, "Something really bad happened");
// manipulate the response here
}
}
}
You could have something like this:
public class UsersService : IUsersService
{
private readonly ILogger<UsersService> _logger;
private readonly UsersContext _context;
// assume UsersContext is an EntityFramework context or some database service
public UsersService(ILogger<UsersService> logger, UsersContext context)
{
_logger = logger;
_context = context;
}
public async Task<User> GetUserAsync(Guid userId)
{
try
{
if (userId == Guid.Empty)
{
throw new InvalidModelException();
}
var user = await _context.FindUserAsync(userId);
if (user == null)
{
throw new UserNotFoundException(userId);
}
return user;
}
catch (InvalidModelException invalidModel)
{
_logger.LogWarning("The received user id is empty");
return null;
}
}
}
And its corresponding controller:
public class UsersController : ControllerBase
{
private readonly IUsersService _usersService;
public UsersController(IUsersService usersService)
{
_usersService = usersService;
}
[HttpGet("userId:guid")]
public async Task<IActionResult> GetUser(Guid userId)
{
var user = await _usersService.GetUserAsync(userId);
if (user == null)
{
return BadRequest();
}
return Ok(user);
}
}
Again, this is just an example to demonstrate how you could approach this, normally you'd do input validation in a more consistent way.

Related

How to call a controller method from middleware and only allow this method to be called from this middleware?

I have a Controller, which has 2 functions and a middleware.
My middleware
try
{
var request = httpContext.Request;
if (/* some logic here */)
{
await _next(httpContext);
// Some logic here
var someReturnValue = someFunction();
// Need to call the Controller Method here
// call SaveStaging(someReturnValue);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
In my Controller
[Authorize(AuthenticationSchemes = "APIKeyAuthentication")]
[LogAction]
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController : ControllerBase
{
private IHttpContextAccessor _accessor;
public MyController(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
[HttpPost]
public async Task<ActionResult<ServiceResult>> PostData([FromBody] DataObj request)
{
ServiceResult result = new ServiceResult();
result.isSuccessful = true;
return Ok(result);
}
public async Task SaveStaging(int logNumber)
{
}
}
So, initially, user will call the PostData endpoint. In this method, it will return that it accepts the data posted by user.
Then in the middleware, I will need to do some things, and finally, I would like to call the SaveStaging method in the Controller. I want this SaveStaging method to be only accessible for the middleware, and should not be called directly by the user.
So my question:
How to call this method from the middleware?
How to limit SaveStaging to be accessible only to the middleware?
You should consider wrapping SaveStaging into a service because it appears to be a cross-cutting concern. Beyond that, it's likely wise to implement a layered architecture as a matter of refactoring.
Since this is .NET Core, I would recommend fully embracing dependency injection and injecting the service into your middleware.
public interface IStagingService
{
Task Save(int logNumber);
}
public class MyController : ControllerBase
{
private readonly IStagingService _service;
private readonly IHttpContextAccessor _accessor;
public MyController(IStagingService service, IHttpContextAccessor accessor)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));;
}
//....
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IStagingService _service;
public RequestCultureMiddleware(RequestDelegate next, IStagingService service)
{
_next = next;
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public async Task InvokeAsync(HttpContext context)
{
// ...
}
In the beginning, your service's will likely start out small and simple. In the event the app grows, the complexity of this layer will likely increase, spawning the need for some facade services (i.e. services composed of services) and maybe some providers for thing like data access.

MediatR CQRS - How to deal with unexisting resources (asp.net core web api)

So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.
If we take DELETE for example, here is my controller action:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});
return NoContent();
}
The Command:
public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}
The Command Handler:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
return Unit.Value;
}
}
As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.
MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:
public class DeleteCourseCommand : IRequest<bool>
...
In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.
Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...
public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...
if (course == null)
return false; // Simple example, where false means it wasn't found.
...
return true;
}
}
The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).
Finally, you'll need to update your Delete action to use the new response type, like this:
public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});
if (!courseWasFound)
return NotFound();
return NoContent();
}
I like returning events from my commands. The command is telling your application what the client wants it to do. The response is what it actually did.
BTW—it's said that command handlers should return anything. That's really only true in a fully async environment where the command won't be completed until sometime after the response to the client that it's accepted. In that case, you would return Task<Unit> and publish these events. The client would get them via some other channel, like a SignalR hub once they were raised. Either way, events are the best way to tell a client what's going on in your application.
Start by defining an interface for your events
public interface IEvent
{
}
Then, create events for each of the things that can happen in a command. You can include information in them if you'd want to do something with that information or just leave them empty if the class itself is enough.
public class CourseNotFoundEvent : IEvent
{
}
public class CourseDeletedEvent : IEvent
{
}
Now, have your command return an event interface.
public class DeleteCourseCommand : IRequest<IEvent>
{
}
Your handler would look something like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course is null)
return new CourseNotFoundEvent();
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
return new CourseDeletedEvent();
}
}
Finally, you can use pattern matching on your web API to do things based on the event that gets returned.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var #event = await Mediator.Send(new DeleteCourseCommand {Id = id});
if(#event is CourseNotFoundEvent)
return NotFound();
return NoContent();
}
I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Result = new JsonResult(
((ValidationException)context.Exception).Failures);
return;
}
var code = HttpStatusCode.InternalServerError;
if (context.Exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new
{
error = new[] { context.Exception.Message }
});
}
}
Startup Class:
services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));
Custom Exception:
public class NotFoundException : Exception
{
public NotFoundException(string entityName, int key)
: base($"Entity {entityName} with primary key {key} was not found.")
{
}
}
Then in the Handle method:
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
else
{
throw new NotFoundException(nameof(Course), request.Id);
}
return Unit.Value;
This seems to do the trick, if anyone can see any potential issues with this please let me know!

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.

Exception handling in OWIN Middlware

I am trying to add some middleware so that any unhandled exceptions I catch and log it but experiencing some difficulties in doing so. Not been able to find a lot on this and for some odd reason my code doesn't seem to be entering the catch block. Seems like it is gracefully handling this and even interrogating the dictionary I can't see the exception.
What I want to happen is, enter the catch block grab the exception and log the stack trace.
The code:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
app.Use(typeof(FooHandler));
app.UseWebApi(config);
}
}
public class FooHandler : OwinMiddleware
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(FooHandler));
public FooHandler(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
if (Logger.IsErrorEnabled)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{ // DOESN'T FALL INTO HERE!
Logger.Error(message, ex);
}
}
}
}
public class FooController : ApiController
{
public Task<IHttpActionResult> Get()
{
throw new Exception("Foo Bar");
}
}
This is because WebApi is handling the exception. You will need to handle exceptions thrown by Controllers in an ExceptionFilterAttribute

Categories

Resources