Chain of responsibility in ExceptionMiddleware .net core - c#

is it possible to implement something similar to chain of responsibility pattern in .net core middleware which catches exceptions? Because I wanted to Handle exceptions globally and take them to their handlers.
Example
try
{
}
catch(CustomException1 ex)
{
}
catch(CustomException2 ex)
{
}
...
The middleware grows really fast and it will be hard to maintain later. I wanted to try{} catch(Exception e) { Handle(e); } and make Handlers for each Exception, for example handler for NullReference etc. I though about solution to take the exception by type and handle it in the handle() method in the specified handler.

I'm toying around with middleware, so in startup:
app.UseMiddleware<ErrorHandlingMiddleware>();
Middleware, I have one general exception handler, you could add many here (sample code, Sentry is an error log service...sentry.io):
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly IHub _sentry;
public ErrorHandlingMiddleware(RequestDelegate next, IHub sentry)
{
_sentry = sentry;
_next = next;
}
public async Task Invoke(HttpContext context/* other dependencies */)
{
try
{
await _next(context).ConfigureAwait(false);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex).ConfigureAwait(false);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
if (exception is ValueNotAcceptedException) code = HttpStatusCode.NotAcceptable;
/*if (exception is MyNotFoundException) code = HttpStatusCode.NotFound;
else if (exception is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
else if (exception is MyException) code = HttpStatusCode.BadRequest;*/
// send to Sentry.IO
_sentry.CaptureException(exception);
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
Note adding a dependency in the constructor will make it a singleton, last the life-cycle of the app (in my case it's fine), or else add dependency in the Invoke.

You can create multiple exception handler IExceptionFilter. After create filters you can inject thats Mvc filters at startup.
Note: mvc filters get hit later than custom middleware.
Note: first added filter get hit last.
services.AddMvc(options =>
{
options.Filters.Add<GeneralExceptionFilter>();
options.Filters.Add<DbExceptionFilter>();
});
Note: if you decide filter should not handle exception, you should not throws exception. You should set ExceptionHandled false
public void OnException(ExceptionContext context)
{
...
context.ExceptionHandled = false;
}
You can also create .net core middlewares and inject it from startup. Concepts are pretty similar to mvc filters.

Related

UseExceptionHandler won't catch tasks exceptions?

I've created a simple webapi .net core 3.1 app.
I want to catch all unhandled exceptions.So I put this code according to the docs :
app.UseExceptionHandler(c => c.Run(async context =>
{
var exception = context.Features
.Get<IExceptionHandlerPathFeature>()
.Error;
var response = new { error = exception.Message };
log.LogDebug(exception.Message);
}));
This is my action:
[HttpGet]
public IActionResult Get()
{
throw new Exception("this is a test");
}
When this code runs, I do see that UseExceptionHandler is working.
But when my code in the action is :
[HttpGet]
public IActionResult Get()
{
Task.Run(async () =>
{
await Task.Delay(4000);
throw new Exception("this is a test");
});
return Ok();
}
Then UseExceptionHandler is NOT working.
However - the following code does catch the task's exception :
AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
Debug.WriteLine(eventArgs.Exception.ToString());
};
Question:
Why does the task exception isn't recognized by UseExceptionHandler?
How can I catch ALL types of exceptions? Should I rely only on AppDomain.CurrentDomain.FirstChanceException?
nb , I did disabled app.UseDeveloperExceptionPage();
To answer your questions.
Why does the task exception isn't recognized by UseExceptionHandler?
As already suggested in the comments, you cannot use UseExceptionHandler to catch exceptions initiated inside non-awaited tasks. UseExceptionHandler wraps your request in ASP.NET Core middleware. Once the action returns OK to the client, the middleware is no longer able to catch any exceptions happening in tasks started from within the action.
How can I catch ALL types of exceptions? Should I rely only on AppDomain.CurrentDomain.FirstChanceException?
You can catch exceptions globally and log them this way if you'd like. But I wouldn't recommend you to do it this way. The only reason you need to implement this event, is that you are starting tasks/threads inside your web requests. You have no way of knowing if these tasks are kept running (application restart, recycle, etc.). If you are looking to launch background tasks with ASP.NET Core, you should use Worker Services which is the intended way of doing this:
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyWorker>();
});
public class MyWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// Do work
}
catch (Exception e)
{
// Log it?
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
The cause of this particular symptom is that Get is starting a fire-and-forget task that the server knows nothing about. The request will complete before the task even has a chance to execute, so the UseExceptionHandler middleware will never see any exceptions. This is no longer a fire-and-forget task.
The real problem though, is executing a long running task in the background. The built-in way to do this is using a Background Service. The docs show how to create timed and queued background service, that act as job queues.
It's equally easy, if not easier, to publish messages with the desired data from, eg a controller to the background service using, eg Channels. No need to create our own queue, when the BCL already has an asynchronous one.
The service could look like this :
public class MyService: BackgroundService
{
private readonly ChannelReader<T> _reader;
public QueuedBspService(MessageQueue<T> queue)
{
_reader = queue.Reader;
}
protected internal async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await foreach (var msg in _reader.ReadAllAsync(stoppingToken))
{
try
{
//Process the message here
}
catch (Exception exc)
{
//Handle message-specific errors
}
}
}
catch (Exception exc)
{
//Handle cancellations and other critical errors
}
}
}
The MessageQueue<T> wraps the Channel, making it easier to inject it to both the BackgroundService and any publishers like eg, a Controller action:
public class MessageQueue<T>
{
private readonly Channel<T> _channel;
public ChannelReader<T> Reader => _channel;
public ChannelWriter<T> Writer => _channel;
public MessageChannel()
{
_channel = Channel.CreateBounded<T>(1);
}
}
I adjusted this code from a service that only allows a single operation at a time. That's a quick&dirty way of preventing controllers from making requests that can't be handled.
On the contolle side, this action will post a request to the queue if possible, and return a Busy response otherwise :
public class MyController
{
private readonly ChannelWriter<T> _writer;
public MyController(MessaggeQueue<T> queue)
{
_writer = queue.Writer;
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult> Post(....)
{
var jobName="SomeJob";
var id=Guid.NewGuid();
var jobMsg=CreateMessage(id,...);
try
{
if (_writer.TryWrite(msg))
{
return CreatedAtAction("GetItem","Jobs",new {id});
}
else
{
return Problem(statusCode:(int) HttpStatusCode.ServiceUnavailable,detail:"Jobs in progress",title:"Busy");
}
}
catch (Exception exc)
{
_logger.LogError(exc,"Queueing {job} failed",jobName);
throw;
}
}
}
The Post action first checks if it can even post a job message. If it succeeds, it returns a 201 - Created response with a URL that could be checked eg to check the status of the jobs. return Created() could be used instead, but once you create a long running job, you also want to check its status.
If the channel is at capacity, the core returns 503 with an explanation

Middleware RequestDelegate not bubbling up

I have an application I would like to add middleware error handling to but the exception never seem to bubble up. I've read several articles about this having to do with async behavior but I can't see what I'm doing wrong.
For example this SO post (Exceptions not bubbling up to Error Handling Middleware?) is very similar but I already have async as that is how it was originally written before we added the middleware error handling.
I'll post what I think is relevant.
ExceptionMiddleware.cs:
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
//we never get here????
await HandleExceptionAsync(context, ex, _options);
}
}
APIService:
public async Task<Response<PaginationModel>> GetPagination(int result, int pageNumber,...)
{
_logger.GetPaginationInformation($"Enter with parameters result: {result},.....");
try
{
....do stuff
}
catch (Exception ex)
{
//we do get here
throw; //return CreateErrorMessage<PaginationModel>("GetPagination", ex);
}
}
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
app.UseCors();
// add http for Schema at default url /graphql
app.UseWebSockets();
app.UseGraphQLWebSockets<ISchema>();
app.UseGraphQL<ISchema>("/graphql");
app.UseGraphQLPlayground();
}
private void FormatExceptionResponse(HttpContext context, Exception exception, Response<PaginationModel> response)
{
response.message = exception.Message;
}
ExceptionMiddlewareExtensions.cs:
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder)
{
var options = new ExceptionOptions();
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder, Action<ExceptionOptions> configureOptions)
{
var options = new ExceptionOptions();
configureOptions(options);
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
}
I set debug breakpoints and everything seems to register and all "hooks" seem to be set and execution flows as expected first through ExceptionMiddleware Invoke _next(context) then to ApiService GetPagination but even if I throw a hard exception or remove the try catch block in GetPagination it never flows back up to Invoke catch?
I'm sure this has something to do with lack of understanding how to handle globally with async Task but I follow the articles on it and it doesn't seem to matter??
Update
Based upon the comment from Andy I'm adding this information, it could be helpful.
GetPagination is NOT an API endpoint. It is a service class called by the GraphQL query.
GraphQL Query:
FieldAsync<Response...>(
"PaginationSearch",
"Returns paginated for specified search terms",
arguments: new QueryArguments(... { Name = "result" },
resolve: async context =>
{
var result = context.GetArgument<int>("result");
//Is this using statement introducing some unexpected behavior as it Disposes behind the scenes??
using (_logger.GetScope("PaginationSearch"))
{
return Service.GetPagination(result...);
}
}
);
Update 2
Moving the registration line to the bottom of the configure method as mentioned in the comments actually makes it so it doesn't flow through the middleware Invoke BUT moving it to the first line does.
To be clear at project start BOTH first line and last line it does flow through invoke. I'm specifically referring to execution when a graphql query is received.
When startup.cs has registration last line Middleware Invoke is not used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint is NOT hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
When startup.cs has registration line first Middleware Invoke is used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
.....
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint **IS** hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
Also not sure if it matters but the API service that is registerd in startup.cs is a singleton.
services.AddSingleton<IAPIService, APIService>();
and the shared HTTP client (using HTTP Typed clients) is added to the services httpclient collection.
services.AddHttpClient<ISecurityClient, SecurityClient>();

How to create custom Exception and Error handler in .Net Core?

I develop a .NET Core 3 REST API using C#.
I read a lot about this topic, and I implemented a custom exception handler middleware, which is work well.
But I realized that the error messages are structured differently.
For example, I use .Net Core Identity and when I try to save a username which is already saved, then I get an HTTP 400 with the following error response: (Sorry about that, I can't post images.)
error Array(1){
0: {
code: "...",
description: ".."
}
}
And, I use too the Attribute validation, which gives this HTTPErrorResponse:
error: {
.
.
.
errors:{
Password: Array(1) {
0: "The field..."
}
}
}
Finally, my custom exception handler class gives an ErroDTO which contains a Code, and a description.
So the problem is there are 3 things, which give 3 different error responses, and I don't know how can I handle this, in Frontend which is an Angular project.
I would like to handle all errors and exceptions by my ErroDto class, but I don't know how to transform the Identity or Attribute errors.
I think that I could test the email and password validation directly in the endpoint instead of using Attribute, and I could test if the existing email, and the Identity won't be throw error. But I think it is not the best practice, and it will be a lot of boilerplate code.
This is my exception handling class:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
ErrorDto error = new ErrorDto();
if (ex is WrongCredentialsException)
{
code = HttpStatusCode.OK;
WrongCredentialsException wrongCredentialsException = (WrongCredentialsException) ex;
error.Code = wrongCredentialsException.Code;
error.Description = wrongCredentialsException.Message;
}
var result = JsonConvert.SerializeObject(new { error });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Create custom Exception class
using System;
[Serializable]
public class EmployeeMgmtException : Exception
{
public EmployeeMgmtException(string message) : base(message)
{
}
}
Create an Exception Middleware class
public class ExceptionMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger<ExceptionMiddleware> logger;
private readonly ResourceManager resourceManager;
}
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware>
logger)
{
this.next = next;
this.logger = logger;
this.resourceManager = new
ResourceManager("EmployeeException.API.Resources.Resource",
typeof(ExceptionMiddleware).Assembly);
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await this.next.Invoke(httpContext);
}
catch (Exception ex)
{
await this.HandleExceptionAsync(httpContext, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
ErrorResponse response = new ErrorResponse();
if (exception is EmployeeMgmtException)
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
response.ErrorMessage = exception.Message;
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
response.ErrorMessage = this.resourceManager.GetString("FailedToProcess",
CultureInfo.CurrentUICulture);
}
this.logger.LogCritical(exception, exception.Message, GetProperties(context));
return context.Response.WriteAsync(response.ToString());
}
}
Register Exception Middleware filter in Startup.cs
app.UseMiddleware<ExceptionMiddleware>();

c# mvc - how to return specific view from generic method

My service layer returns error code(s), and I have to return corresponding View. But with many possible error codes my controller methods will have big switch case. So I decided to inherit all controllers from some base controller which has this method:
public IActionResult GetResponse<T>(ServiceResponse<T> serviceResponse)
{
switch (serviceResponse.Result)
{
case ServiceResult.Ok: return Ok(serviceResponse.ResponseObject);
case ServiceResult.ValidationError: return BadRequest(serviceResponse.Messages);
case ServiceResult.NoPermission: return Unauthorized(serviceResponse.Messages);
case ServiceResult.NotFound: return BadRequest(serviceResponse.Messages);
default: return BadRequest();
}
}
Now, the problem is, input parameter for this method comes from service layer. Service layer returns me entity object. I need to somehow create ViewModel from this entity object. I can pass a parameter that indicates needed ViewModel type and make another switch block, but I don't think it's the best option. Is there any way to map entity to view model object in my case?
You may add an error handing middleware such as below and hook it in the pipeline. Of course you're gonna need to modify this for your own story.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
if (ex is CustomExceptionOne) code = HttpStatusCode.BadRequest;
if (ex is CustomExceptionTwo) code = HttpStatusCode.Unauthorized;
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
and in the startup configure method:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));

MediatR fluent validation response from pipeline behavior

I have a MediatR Pipeline behavior for validating commands with the FluentValidation library. I've seen many examples where you throw a ValidationException from the behavior, and that works fine for me. However in my scenario I want to update my response object with the validation errors.
I am able to build and run the following code. When I set a break point within the if statement the CommandResponse is constructed with the validation errors as expected - but when the response is received by the original caller it is null:
public class RequestValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext(request);
// Run the associated validator against the request
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if(failures.Count != 0)
{
var commandResponse = new CommandResponse(failures) { isSuccess = false };
return commandResponse as Task<TResponse>;
}
else
{
return next();
}
}
}
I think it has to do with my attempt to cast it as Task - but without this I get compiler errors. I'm returning the same type that my command handler would if validation passes so I am at a loss as to why it returns a null instance of the expected response. I feel like there is a better way to handle this, but I've tried a number of variations to no avail. Any suggestions? Is there a better pattern to use? I'd prefer to keep this in the pipeline as it will be reused a lot.
I ended up adding exception handling middleware to the MVC project. Instead of trying to pass back the validation errors as an object I throw a ValidationException inside of the pipeline behavior and the middleware handles any and all exceptions across the entire project. This actually worked out better as I handle all exceptions in one place higher up in the processing chain.
Here is the updated portion of the code I posted:
if(failures.Count != 0)
{
// If any failures are found, throw a custom ValidationException object
throw new ValidationException(failures);
}
else
{
// If validation passed, allow the command or query to continue:
return next();
}
Here is the exception handling middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Log issues and handle exception response
if (exception.GetType() == typeof(ValidationException))
{
var code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(((ValidationException)exception).Failures);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
else
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { isSuccess = false, error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
}
You then register the middleware in your Startup before MVC is added:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
}
Note: You can also create an extension method for your middleware:
public static class ErrorHandlingMiddlewareExtension
{
public static IApplicationBuilder UseErrorHandlingMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
Which allows you to register it like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseErrorHandlingMiddleware();
app.UseMvc();
}
I am using .Net core 3.1 and I was not able to catch the exceptions when I added the middleware before the following block in Configure function of Startup
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
check in the configure method. Make sure to register it after the above statement like this. It is quite obvious but may help someone like me.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware<ErrorHandlingMiddleware>();

Categories

Resources