McGlothlin
McGlothlin

Reputation: 2099

Web API middleware short circuiting unexpectedly

The following code is part of an error handling middleware, whose goal is to provide consistent formatting for the client even if an error is thrown.

I am trying to serialize a response as XML when the Accept header is set to application/xml, otherwise return JSON. This article helped get me started: https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api

if (context.Request.Headers["Accept"] == "application/xml")
{
    context.Response.ContentType = "application/xml";

    using (var stringwriter = new StringWriter())
    {
        var serializer = new XmlSerializer(response.GetType());
        serializer.Serialize(stringwriter, response);
        await context.Response.WriteAsync(stringwriter.ToString());
    }
}
else {
    context.Response.ContentType = "application/json";
    var json = JsonConvert.SerializeObject(response);
    await context.Response.WriteAsync(json);
}

The else block works as expected. If I set a breakpoint on the line where the XmlSerializer is declared, execution halts. If I set a breakpoint on the following line, the breakpoint is never hit; a response has already been sent to the client.

My middleware is configured like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseStatusCodePagesWithReExecute("/error/{0}");
        app.UseExceptionHandler("/error/500");
        app.UseHsts();
        app.UseMiddleware<ErrorWrappingMiddleware>();
        app.UseMvc();
    }

Why is a response returned to the client before context.Response.WriteAsync(stringwriter.ToString()); is called in the if block?

Upvotes: 0

Views: 645

Answers (1)

McGlothlin
McGlothlin

Reputation: 2099

I figured out the problem, it was a silly mistake.

To test 500 errors, I was intentionally adding a property to a model that didn't have a corresponding column in the database. This throws a SqlException on an invalid column name. There was however a second exception I missed that told me exactly what the problem was:

ApiResponse cannot be serialized because it does not have a parameterless constructor.

This occurred in the call to serializer.Serialize(stringwriter, response). The example I referenced creates an ApiResponse class (the response I attempted to serialize). However this class does not have a parameterless constructor in the example I was learning from, and I wasn't aware this was necessary until I found this answer. This second exception halted execution, but since I forgot to turn on development mode and I was trying to throw an exception, I didn't notice the second one.

Upvotes: 1

Related Questions