Middleware RequestDelegate not bubbling up - c#

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>();

Related

Logging in the middleware exception handler

In a layered web application I want to move all error logging from the Domain and Data layers to the global exception handler, but I'm not sure what is the trade-off. I want to remove any logging call and replace it by a more specific Exception (custom if it's necessary) or remove the catching:
try{
. . .
}
catch
{
Logger.Error('Info'); // <-- remove this for a: throw new CustomException('Info', ex);
throw; // <-- then, remove this line
}
There is a configured Global Exception Handler as middle-ware in the WebAPI, then as part of the handler method I'm going to log any exception occurred
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(
error =>
{
GlobalExceptionHandler.ErrorHandling(error, env);
});
}
// GlobalExceptionHandler.cs
public static class GlobalExceptionHandler
{
public static void ErrorHandling(IApplicationBuilder errorApp, IHostingEnvironment env)
{
errorApp.Run(async context =>
{
.
.
.
Log.Current.Error(exception.Message, () => exception);
}
}
}
Could be a better approach to avoid duplicated logging records?
In the applications I build I like to use the approach you are suggesting. I'll post the middleware that I use:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MyProject.Interfaces;
namespace MyProject.Middlewares
{
public class ErrorReporterMiddleware
{
private readonly RequestDelegate RequestDelegate;
public ErrorReporterMiddleware(RequestDelegate requestDelegate)
{
RequestDelegate = requestDelegate ?? throw new ArgumentNullException(nameof(requestDelegate));
}
public async Task Invoke(HttpContext httpContext, IErrorReporter errorReporter)
{
try
{
await RequestDelegate(httpContext);
}
catch (Exception e)
{
await errorReporter?.CaptureAsync(e);
throw;
}
}
}
}
In this case IErrorReporter is an interface I have defined in the MyProject.Interfaces namespace. I use it to abstract the logging service:
using System;
using System.Threading.Tasks;
namespace MyProject.Interfaces
{
public interface IErrorReporter
{
Task CaptureAsync(Exception exception);
Task CaptureAsync(string message);
}
}
Then in the Startup.cs I just add the following line to the Configure method:
app.UseMiddleware<ErrorReporterMiddleware>();
Nothing special but I think it's a clean approach.

Handling 404 in MVC Core API Project

I'm trying to figure out where I went wrong. I want to catch 404 errors that come to my api. I have the middleware setup, but the exception never occurs when I try to hit the page that doesn't exist.
public async Task Invoke(HttpContext context)
{
try
{
await _requestDelegate.Invoke(context);
}
catch (Exception exception)
{
await HandleExceptionAsync(context, exception);
}
}
//in startup
app.UseMiddleware<ExceptionHandler>();
and I register it in the startup, its the first thing I do to ensure it handles the rest.
Sounds like you want UseStatusCodePagesWithReExecute to catch the 404 and modify the response, log, ect.
//Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseStatusCodePagesWithReExecute("/error/", "?statusCode={0}");
...
}
//SomeController.cs
public IActionResult Error(int? statusCode = null)
{
if(statusCode == 404) return new ObjectResult(new { message = "404 - Not Found" });
...
}

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>();

Chain of responsibility in ExceptionMiddleware .net core

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.

UseExceptionHandler is being ignored

This is my Startup() method in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
loggerFactory.AddNLog();
}
app.UseApplicationInsightsRequestTelemetry(); // Needs to be first
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorResponse
{
Code = 500,
Message = ex.Message
}.ToString(), Encoding.UTF8);
}
});
});
app.UseApplicationInsightsExceptionTelemetry(); // After error page
app.UseMvc();
}
This is my route in a controller.
[HttpGet]
[Route("Test")]
public string Test()
{
if(!string.IsNullOrEmpty(Request.Query["test"]))
throw new Exception("Testing..");
return "Hello.";
}
ErrorResponse.cs
public class ErrorResponse
{
public int Code { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
When I go to http://website/test it shows "Hello." but when I go to http://website/test?test=asd it throws my exception (the one in the route) instead of catching it with UseExceptionHandler and stops the program.
My goal is to make it show the following json instead:
{
"Error": 500,
"Message": "Testing..."
}
I am using asp.net core.
Old post, but I just hit the same issue.
There is likely some exception filter applied globally. Look up the MVC configuration in Startup.ConfigureServices. I had services.AddMvcOptions(o => o.Filters.Add<ErrorHandlerAttribute>()) which was the problem.
Your app is stopped on exception cause you run it in debug mode in VS and Debugger is attached. Run app from console for example (or disable "break on exception" option in VS.
In my case it was that app.UseDeveloperExceptionPage() was already catching the exceptions and conflicting in some way with this middleware.
}

Categories

Resources