简体   繁体   中英

Catch exception thrown from Custom JsonConverter

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM