Handling 404 in MVC Core API Project - c#

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" });
...
}

Related

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

ErrorHandling Middleware catch is never caught

I have this ErrorHandlingMiddleware that looks like this:
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 statusCode = (int)HttpStatusCode.InternalServerError;
if (ex is NotFoundError) statusCode = (int)HttpStatusCode.NotFound;
//else if (ex is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
//else if (ex is MyException) code = HttpStatusCode.BadRequest;
var error = new AttemptError(statusCode, ex.Message, ex);
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
return context.Response.WriteAsync(error.ToString());
}
}
And I have added this to my Startup class:
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<ErrorHandlingMiddleware>();
app.SeedIdentityServerDatabase();
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "r3plica Identity Server v1");
c.OAuthClientId("swagger");
c.OAuthAppName("Swagger Api UI");
});
app.UseMvc();
}
I would expect that if I was anywhere in my application and I throw an exception, it would be caught and it would execute this line:
await HandleExceptionAsync(context, ex);
So, I set up a test:
throw new Exception();
Which is thrown in my controller. When I run my application and then call the endpoint that has that exception thrown, it does indeed get to the Invoke method of my ErrorHandlingMiddleware, but instead of an exception being caught, it just goes to the await _next(context)....
Does anyone know what I am doing wrong?
Your middleware will work if you place the call to use it after app.UseDeveloperExceptionPage(); in Startup.
Handle errors in ASP.NET Core says
Place the call to UseDeveloperExceptionPage before any middleware that you want to catch exceptions.
I fixed this by creating a filter:
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
public int Order { get; set; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context) { }
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception == null) return;
var attempt = Attempt<string>.Fail(context.Exception);
if (context.Exception is AttemptException exception)
{
context.Result = new ObjectResult(attempt)
{
StatusCode = exception.StatusCode,
};
}
else
{
context.Result = new ObjectResult(attempt)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
}
context.ExceptionHandled = true;
}
}
And registering it like this:
services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));

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

Show exception created in EF Core repository instead of 500 Error

My goal is to verify if an object's name already exists in my EF Core db, if so: throw a specific error. However, I receive a 500 internal server error.
First I created an index on name in DbContext, including IsUnique and some code to catch the exception in the repository.
Can I maybe add something in the controller that says if errorcode == 2601 then throw "the required exception"? Or is there another way to overcome this 500 error? Thanks in advance for the help!
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasIndex(c => c.Name)
.IsUnique();
}
Repository:
public async Task<bool> SaveAsync()
{
try
{
return (await _context.SaveChangesAsync() >= 0);
}
catch (DbUpdateException dbEx)
{
SqlException sqlException = dbEx.InnerException as SqlException;
if (sqlException.Number == 2601)
{
throw new Exception("Name already exists. Please provide a different name.");
}
throw new Exception(dbEx.Message);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
Controller:
Public async Task<IActionResult> AddCar([FromBody] Car car)
...
if (!await _repository.SaveAsync())
{
throw new Exception("Fail on save...");
}
...
If you are using ASP.Net Core, you can create you own exception handling middleware.
The error handling middleware class itself may look something like:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate m_next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
m_next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await m_next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
string message = "Something is wrong!";
if (exception is MyException)
{
httpStatusCode = HttpStatusCode.NotFound; // Or whatever status code you want to return
message = exception.Message; // Or whatever message you want to return
}
string result = JsonConvert.SerializeObject(new
{
error = message
});
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(result);
}
}
You register in Startup.Configure() as:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
Change stdoutLogEnabled="false" to true and then check the logs at stdoutLogFile=".\logs\stdout". The error(s) there might tell you something.
Check that you set up right Environment Name using ASPNETCORE_ENVIRONMENT environment variable as so use correct settings like connection string. On your machine by default you have "Development" environment.
You may use Error Handling middlewares for showing exceptions like
app.UseDeveloperExceptionPage();

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.

Categories

Resources