Christophe Blin
Christophe Blin

Reputation: 1909

Producing a 400 BadRequest when invalid JsonConverter?

I'm using a custom JsonConverter with System.Text.Json in order to have DateTime with required Timezone

/// <summary>
/// Converts a string to/from DateTime, requiring the timezone
/// </summary>
public class JsonDateTimeWithTimezoneConverter : JsonConverter<DateTime>
{
    //
    // FOR EXPLANATIONS
    //
    // see https://stackoverflow.com/a/58103218/1545567 and https://stackoverflow.com/a/17752389/1545567
    //



    private readonly string[] FORMATS = { 
        // Basic formats
        "yyyyMMddTHHmmsszzz",
        "yyyyMMddTHHmmsszz",
        "yyyyMMddTHHmmssZ",
        // Extended formats
        "yyyy-MM-ddTHH:mm:sszzz",
        "yyyy-MM-ddTHH:mm:sszz",
        "yyyy-MM-ddTHH:mm:ssZ",
        // All of the above with reduced accuracy to minutes
        "yyyyMMddTHHmmzzz",
        "yyyyMMddTHHmmzz",
        "yyyyMMddTHHmmZ",
        "yyyy-MM-ddTHH:mmzzz",
        "yyyy-MM-ddTHH:mmzz",
        "yyyy-MM-ddTHH:mmZ",            
    };

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        //TODO: 400 BadRequest instead of error 500 InternalServerError when format not respected
        Debug.Assert(typeToConvert == typeof(DateTime));
        return DateTime.ParseExact(reader.GetString(), FORMATS, CultureInfo.InvariantCulture, DateTimeStyles.None);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:sszzz"));
    }
}

I register it like this in ConfigureServices

        services.AddControllers()
            .AddJsonOptions(options => {
                options.JsonSerializerOptions.Converters.Add(new JsonDateTimeWithTimezoneConverter());
            });

Like you can see in the code, when the date is received without timezone, it will crash with an exception that will cause an error 500 InternalServerError

How can I throw an exception in order to return a 400 WITHOUT touching the use app.UseExceptionHandler (because the code is inside my library) ?

Note that the exception that is thrown is FormatException so, to me, it should be translated to BadRequest ...

Upvotes: 3

Views: 1060

Answers (1)

scharnyw
scharnyw

Reputation: 2686

This issue should be fixed as of ASP.NET Core 3.1: FormatException (which is thrown by the DateTime.ParseExact() method) is now treated as model state errors in SystemTextJsonInputFormatter, which is the default input formatter since 3.0. This will cause a 400 instead of 500 status to be returned. See the PR on Github, and the relevant code sections for 3.0 and 3.1;

If you are still using 3.0, you could catch the FormatException and rethrow a JsonException, which is the only type of exception treated as model state errors in 3.0:

try
{
    return DateTime.ParseExact(reader.GetString(), FORMATS, CultureInfo.InvariantCulture, DateTimeStyles.None);
}
catch(FormatException e)
{
    throw new JsonException();
}

Upvotes: 3

Related Questions