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):
400
error instead of 500
andError
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()
.