Catch exception thrown from Custom JsonConverter - c#

I am looking for a way to catch an exception thrown from a custome Newtonsoft's JsonConverter.
I created the following custom converter. JsonConverter attribute in Config class uses it. Config class is used to post a config object and used for Web API POST method (I'm using .NET Core 3.1).
The converter works fine but when an exception is thrown, the middleware that handles exceptions does not catch it. For instance, I expected that the middleware would catch MissingConfigTypeException when type is null in the HTTP request body, but the Func in appBuilder.Run() never gets called. Any exceptions thrown from the converter are never caught by the middleware.
Because the exception is not processed by the middleware, the API method returns http status code 500 with no HTTP response body. I want to return 400 with my custom error message.
My goals are (I need to achieve both):
Return http 400 error instead of 500 and
Return my custom error in HTTP response body (Error object. See the middleware below)
I wonder if there is a way to catch an exception somehow (using the middleware or otherwise) or modify HTTP response body (I'll have to be able to identify that a particular error occurred so I can modify the response body only when the error occurs)
Note: I don't want to use ModelState in my controller action method (don't want to add some sort of error checking code for each method).
Update
The middleware can catch exceptions thrown from controller action methods.
My custom converter:
public class ConfigJsonConverter : JsonConverter
{
public override object ReadJson(
JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
...
var jObject = JObject.Load(reader);
if (jObject == null) throw new InvalidConfigException();
var type = jObject["type"] ?? jObject["Type"];
if (type == null) throw new MissingConfigTypeException();
var target = CreateConfig(jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
private static Config CreateConfig(JObject jObject)
{
var type = (string)jObject.GetValue("type", StringComparison.OrdinalIgnoreCase);
if (Enum.TryParse<ConfigType>(type, true, out var configType))
{
switch (configType)
{
case ConfigType.TypeA:
return new ConfigA();
case ConfigType.TypeB:
return new ConfigB();
}
}
throw new UnsupportedConfigTypeException(type, jObject);
}
Config class:
[JsonConverter(typeof(ConfigJsonConverter))]
public abstract class Config {...}
public class ConfigA : Config {...}
The middleware:
// This is called in startup.cs
public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder application)
{
return application.UseExceptionHandler(appBuilder => appBuilder.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
Error error;
switch (exception)
{
case InvalidConfigException typedException:
error = new Error
{
Code = StatusCodes.Status400BadRequest,
Message = typedException.Message
};
break;
case MissingConfigTypeException typedException:
error = new Error
{
Code = StatusCodes.Status400BadRequest,
Message = typedException.Message
};
break;
.....
}
var result = JsonConvert.SerializeObject(error);
context.Response.ContentType = "application/json";
context.Response.StatusCode = error.Code;
await context.Response.WriteAsync(result);
}));
}
Update:
Startup.cs
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
if (EnableHttps)
app.UseHsts();
...
app
.UseForwardedHeaders()
.UsePathBase(appConfig.BasePath);
if (EnableHttps)
app.UseHttpsRedirection();
app
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
endpoints.MapControllers();
})
.UseCustomExceptionHandler(logger);

Try adding your UseCustomExceptionHandler before setting up endpoints and routing:
app
.UseCustomExceptionHandler(logger)
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
endpoints.MapControllers();
});
Also based on docs exception handling usually is set up one of the first in pipeline, even before app.UseHsts().

Related

Map custom exceptions to HTTP exceptions at one place

In my .NET Core Web API project many controller endpoints have code like this example
public async Task<ActionResult<User>> UpdateUserUsernameAsync(/* DTOs */)
{
try
{
User user = null; // Update user here
return Ok(user);
}
catch (EntityNotFoundException entityNotFoundException) // map not found to 404
{
return NotFound(entityNotFoundException.Message);
}
catch (EntityAlreadyExistsException entityAlreadyExistsException) // map duplicate name to 409
{
return Conflict(entityAlreadyExistsException.Message);
}
catch (Exception exception) // map any other errors to 500
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
I would like to create a mapping for the controllers that catches exceptions and maps them to HTTP responses before sending them back to the client.
A similiar question has been asked 4 years ago
ASP.NET Core Web API exception handling
In NestJs it's possible to define your own mappings by extending a base class e.g.
export class MyCustomException extends HttpException {
constructor() {
super('My custom error message', HttpStatus.FORBIDDEN);
}
}
Taken from here https://docs.nestjs.com/exception-filters#custom-exceptions
So basically I want to define mapping classes that could look like this sample code (this just shows my pseudo implementation)
// Map MyCustomException to NotFound
public class MyCustomExceptionMapping<TCustomException> : IExceptionMapping<TCustomException>
{
public ActionResult Map(TCustomException exception)
{
return NotFound(exception.Message);
}
}
Next I can cleanup the controller endpoint method to
public async Task<ActionResult<User>> UpdateUserUsernameAsync(/* DTOs */)
{
User user = null; // Update user here
return Ok(user);
}
Whenever an exception gets thrown the API would try to find the correct mapping interface. Otherwise it sends back a 500.
It would be nice to define such mappings and avoid a huge switch case for every exception in your project.
Does something like this exists? Is the accepted answer from the previous question still up to date?
Use Exception Filters it will be called when the controller throws an Exception and you can define the custom response. Microsoft Docs
public class MyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
HttpStatusCode status;
var message = "";
var exceptionType = context.Exception.GetType();
if (exceptionType is EntityNotFoundException)
{
status = HttpStatusCode.NotFound;
message = context.Exception.Message;
}
else if (exceptionType is EntityAlreadyExistsException)
{
status = HttpStatusCode.Conflict;
message = context.Exception.Message;
}
else
{
status = HttpStatusCode.InternalServerError;
message = "Internal Server Error.";
}
//You can enable logging error
context.ExceptionHandled = true;
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)status;
response.ContentType = "application/json";
context.Result = new ObjectResult(new ApiResponse { Message = message, Data = null });
}
}
To use the filter on all controllers you must register it in the ConfigureServices method in the Startup.cs
services.AddMvc(config =>
{
config.Filters.Add(typeof(MyExceptionFilter));
})

Can't implement invokable catch of try-catch in middleware in Core pipeline

I've added a custom middleware to my Startup class (following the example here).
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use((context, next) =>
{
try { return next(); }
catch (Exception exception) { return new Task(() => { }); }
});
app.UseCors("Welcome All");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(_ => _.SwaggerEndpoint("/swagger/v0.1/swagger.json", "Version 0.1"));
}
It was my understanding that throwing an exception in my method in a controller should be caught in try-catch of the custom middleware. But .NET Core seems to diagree with my expectations. By the breakpoints I can see that return next() is invoked but after the controller's method throws an exception, the exception isn't caught and the yellow marker jumps past the middleware alltoghether and lands at the end curly brace of it.
What am I missing?
If it's of any significance or interest, my aim is to move out the exception handling from my methods to the cross-cut middy in orde to simplify the code (and I want not to apply filters, since I'm targetting pure WebApi without MVC). So the controller and the method look something like this.
[Route("api/test")]
public class TestController : Controller
{
public TestController(...) { ... }
...
[HttpPost]
public IActionResult CrashBoom([FromBody] Thing input)
{
if (input.isCrashworthy)
throw new Exception("Boom")
else
return Ok();
}
}
The problem here is that request delegates, the executable parts of a middleware, run asynchronously. If you look at the type of next you can see that it is actually a Func<Task>, so it’s a function that returns a task. Or in other words: It’s an asynchronous function without a return value.
That means that in order to be able to catch exceptions, you need to keep the asynchronous context. So your custom middleware should execute asynchronously as well. If you do that, you will notice that you can catch exceptions and then respond to them accordingly, for example by writing out a plain text response (as an example):
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
// let’s log the exception
var logger = context.RequestServices.GetService<ILogger<Startup>>();
logger.LogWarning(ex, "Encountered an exception");
// write a response
context.Response.StatusCode = 500;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Sorry, something went wrong!");
}
});
Now, if an exception is raised further down the middleware pipeline, then your custom pipeline will be able to catch it and respond to it.

Web API 2 services - how to return an Exception message in the Status

Is it possible, in Web API 2 to directly return the Exception message in the response's Status ?
For example, if I was writing a WCF Service (rather than Webi API), I could follow this tutorial to directly return an Exception message as part of the response status:
Here, the web service doesn't return any data in the Response, and the error message gets returned directly in the Status Description.
This is exactly what I'd like my Web API services to do when an exception occurs, but I can't work out how to do it.
Most suggestions suggest using code like below, but then the error message will then always get returned in a separate response string, rather than being part of the Status.
For example, if I were to use this code:
public IHttpActionResult GetAllProducts()
{
try
{
// Let's get our service to throw an Exception
throw new Exception("Something went wrong");
return Ok(products);
}
catch (Exception ex)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode)500,
new HttpError("Something went wrong")));
}
}
... then it returns a generic 500 message, and the exception is returned in a JSON string.
Does anyone know how to modify a Web API function (which returns an IHttpActionResult object) to do this ?
You could register a custom global filter that will handle all Exceptions. Something like:
public class CatchAllExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(context.Exception.Message)
};
}
}
You will need to register it in WebApiConfig.cs, with:
config.Filters.Add(new CatchAllExceptionFilterAttribute());
This filter will be hit everytime there is an unhandled exception in the system and set the http response to the exception message. You could also check the different types of exception and alter your response accordingly, for example:
public override void OnException(HttpActionExecutedContext context)
{
if(context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented)
{
Content = new StringContent("Method not implemented.")
};
}
else
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(context.Exception.Message)
};
}
}
https://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling
Please refer above link, it will help you!

ASP.NET 5 API return exception to caller

I'm switching from ASP.NET 4.5 to ASP.NET 5 and am using it to generate some RESTful web services. In 4.5 I was able to throw an exception inside of an action and have it get back to the caller. I want to do that in ASP.NET 5, but I have had no luck doing so yet. I want to avoid using a Try/Catch on every action to accomplish this.
ASP.NET information from Visual Studio about window: ASP.NET and Web Tools 2015 (RC1 Update 1) 14.1.11120.0
Here is an example of the code I'm using to test this.
[Route("[controller]")]
public class SandController : Controller
{
/// <summary>
/// Test GET on the webservice.
/// </summary>
/// <returns>A success message with a timestamp.</returns>
[HttpGet]
public JsonResult Get()
{
object TwiddleDee = null;
string TwiddleDum = TwiddleDee.ToString();
return Json($"Webservice successfully called on {DateTime.Now}.");
}
}
I'm able to call this action and see my breakpoint hit, but on the calling end I receive a 500 error code and no body in the response.
Edit 1:
I changed my example to reflect this, but I want to return the exception information to a caller in the situation I have an unexpected exception, not one I have thrown myself. The code is an example, I'm aware that particular situation could be solved by a null ref check.
Edit 2:
#danludwig pointed out MSDN documentation for middleware which generated this solution:
private void ConfigureApp(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler();
app.UseStaticFiles();
// Adding middleware to catch exceptions and handle them
app.Use(async (context, next) =>
{
try
{
await next.Invoke();
}
catch (Exception ex)
{
context.Response.WriteAsync($"FOUND AN EXCEPTION!: {ex.Message}");
}
});
app.UseMvc();
}
I want to avoid using a Try/Catch on every action to accomplish this.
https://docs.asp.net/en/latest/fundamentals/middleware.html
Note that middleware also means you don't need to add any ExceptionFilterAttributes
You can achieve that by using an ExceptionFilterAttribute. You will need one for each type of exception that you want to catch. You then need to register it in FilterConfig.cs
public class RootExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is Exception)
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
// or...
// context.Response.Content = new StringContent("...");
// context.Response.ReasonPhrase = "random";
}
}
}

Handling error from binding action parameters to route values of incorrect types

I'm having troubles handling all types of errors in ASP.NET WebAPI.
I've successfully handled exceptions thrown inside my action methods using an ExceptionFilter and 404 errors for invalid routes, invalid controller or action name. However, I'm struggling to handle the error where the controller and action are both found, but the parameters for model binding are incorrect types.
Here's my action, which is routed to /api/users/{id}.
[HttpGet]
public virtual TPoco Get(long id)
{
...
}
If I request the URL /api/users/notinteger, I get a 400 Bad Request error that is handled outside of my code:
{
Message: "The request is invalid.",
MessageDetail: "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int64' for method '___ Get(Int64)' in '___'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
How can I intercept this error and respond with my own error message? Preferably not in the controller itself as I'd like to handle several controllers in the same way.
I've tried using global.asax.cs's Application_Error event as per this question, but that doesn't seem to be called in this case.
It appears that these errors are added to the ModelState as model binding errors. The action selector selects the correct action and the action invoker invokes it without throwing any errors.
The workaround I came up with is to create an action invoker that checks the ModelState for errors. If it finds any, it passes the first one to the exception handling method used by my ExceptionFilter and ErrorController.
internal class ThrowModelStateErrorsActionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
foreach (var error in actionContext.ModelState.SelectMany(kvp => kvp.Value.Errors))
{
var exception = error.Exception ?? new ArgumentException(error.ErrorMessage);
//invoke global exception handling
}
return base.InvokeActionAsync(actionContext, cancellationToken);
}
}
It's nasty, but it works. This has taken up most of my day and I'm just glad to have finally got somewhere.
I'd be interested to know what the consequences are to this. What else uses the ModelState errors in Web API? Could anyone add some possible flaws in this solution?
It will more better if you use the new WebApi 2.1 Global error handling as discussed here,
http://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling&referringTitle=Specs
http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error
If you are not willing to use WebApi 2.1 for a valid reason then you can try. (Note I have not tested but you can try). Create a custom action descriptor by inheriting with ReflectedHttpActionDescriptor and handle ExecuteAsync. This is what I mean,
public class HttpNotFoundActionDescriptor : ReflectedHttpActionDescriptor
{
ReflectedHttpActionDescriptor _descriptor;
public HttpNotFoundActionDescriptor(ReflectedHttpActionDescriptor descriptor)
{
_descriptor = descriptor;
}
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
{
try
{
return descriptor.ExecuteAsync(controllerContext, arguments, cancellationToken);
}
catch (HttpResponseException ex)
{
//..........................
}
}
}
public class HttpNotFoundAwareControllerActionSelector : ApiControllerActionSelector
{
public HttpNotFoundAwareControllerActionSelector()
{
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
HttpActionDescriptor decriptor = null;
try
{
decriptor = base.SelectAction(controllerContext);
}
catch (HttpResponseException ex)
{
var code = ex.Response.StatusCode;
if (code != HttpStatusCode.NotFound && code != HttpStatusCode.MethodNotAllowed)
throw;
var routeData = controllerContext.RouteData;
routeData.Values["action"] = "Handle404";
IHttpController httpController = new ErrorController();
controllerContext.Controller = httpController;
controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "Error", httpController.GetType());
decriptor = base.SelectAction(controllerContext);
}
return new HttpNotFoundActionDescriptor(decriptor);
}
}
Note you need to override all the virtual methods.

Categories