aspnet-api-versioning: Problems using UseExceptionHandler() and UseStatusCodePagesWithReExecute()

I am currently upgrading my Web Api from Asp.Net Core v1 with Versioning v1.0 to Asp.Net Core v2 and Versioning v2 (basically the latest stable versions of both). We are targeting .Net Framework, but its the same for .Net Core.

In our existing implementation we use the following in our startup class so we can add some content when errors occur:

         app.UseExceptionHandler("/api/v1.0/error");
         app.UseStatusCodePagesWithReExecute("/api/v1.0/error");

This is a simple error controller to go with it (not our actual one!):

   [Produces("application/json")]
   [Route("api/v{version:apiVersion}/error")]
   public class ErrorController : Controller
   {

      public IActionResult Index()
      {
         return Json(new { status = Response.StatusCode });
      }
   }

This worked fine in v1.0 of api versioning, however I am having problems now I have upgraded.

What happens is that the first time an exception occurs, or a 400+ status code is returned, the error controller is not called and an empty response containing the error code is returned to the client. On subsequent calls it works OK.

This is easily reproduced by adding the above code to the standard Web Api Asp.Net Core template in VS2017, adding Api versioning to the ValuesController and throwing exceptions or returning 400+ status codes from the controller methods:

   [ApiVersion("1.0")]
   [Route("api/v{version:apiVersion}/values")]
   public class ValuesController : Controller
   {
      // GET api/values
      [HttpGet]
      public IActionResult Get()
      {
         throw new Exception("Whoops!");
         //return BadRequest();
         //return Ok(new string[] { "value1", "value2" });
      }

      // GET api/values/5
      [HttpGet("{id}")]
      public string Get(int id)
      {
         return "value";
      }

      // POST api/values
      [HttpPost]
      public void Post([FromBody]string value)
      {
         throw new Exception("Whoops!");
      }

      // PUT api/values/5
      [HttpPut("{id}")]
      public void Put(int id, [FromBody]string value)
      {
      }

      // DELETE api/values/5
      [HttpDelete("{id}")]
      public void Delete(int id)
      {
      }
   }

Currently this problem is delaying my upgrade, so it would be great to get a fix or simple workaround!

thanks

Bill

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 16

Most upvoted comments

Glad you got something working. I definitely don’t like that these oddities result in a behaviors that developers don’t expect. I support POLA. I’ll continue to carry the sentiment forward with the ASP.NET team. To remove this from API versioning, there needs to be a way to retrieve all candidates up front and/or ensure that the IActionSelector is only called once or notify the IActionSelector that there won’t be any more passes.

This behavior didn’t exist in 1.0.* because it assumed that the action selection process only occurred once. That actually led to a different set of bugs. To resolve that issue, the design had to change in 1.1.*+ to capture all candidates and only make a decision at the very end. After at least one pass has been made for a route, the action selection process can short-circuit. That’s why things may not behave correctly on the first pass, but then seem fine afterward.

I’m still not entirely sure why you want to do this for an API, but this configuration yields error text correctly every time:

app.UseExceptionHandler(
    new ExceptionHandlerOptions
    {
        ExceptionHandler = async context =>
        {
            context.Response.ContentType = "text/plain";
            await context.Response.WriteAsync( "Welcome to the error page." );
        }
    } );
app.UseMvc();

That probably isn’t exactly what you’re looking for, but hopefully it points you in the right direction.

Sounds reasonable. After thinking upon this issue some more, I realized there may be a few more options for you.

First, this issue should never affect the behavior of unhandled exceptions because the pipeline will be interrupted. This could also be handled with an ExceptionFilter. I believe you can even configure the error handler differently for UI and API-based routes (but I’ve never tried it).

If you’re only interested in the error response hooks specific to API versioning (e.g. 400/405), there are options available. Granted, it’s not the convenient one-stop shop, but there is a way. To change things, you just need to replace the default error response provider. The error responses topic will describe what you can expect to receive. Since the provider ultimately returns IActionResult, you can return pretty much whatever you want.

Give those topics a peek. It may be much easier and get you what you want compared to the alternatives.