Andrzej Lichnerowicz Andrzej Lichnerowicz - 2 months ago 69
ASP.NET (C#) Question

ASP.Net Core CreatedAtAction in HttpPost action returns 201 but entire request ends with 500

I'm following[1] for setting up my first .NET Core WebAPI (moving from "old" WebAPI). When I do HttpPost action, as in tutorial:

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
TodoItems.Add(item);
return CreatedAtAction("GetTodo", new { id = item.Key }, item);
}


I get a HttpError 500 after exiting my Controller.
CreatedAtAction
returns proper object with Status Code 201 as I would expect, and after my method exits the Server somehow turns it into 500. I don't seem to be able to step into the rest of the pipeline. Trace gives me following error:

81. -GENERAL_REQUEST_ENTITY
82. -NOTIFY_MODULE_COMPLETION
83. -MODULE_SET_RESPONSE_ERROR_STATUS [Warning] 171ms

ModuleName AspNetCoreModule
Notification EXECUTE_REQUEST_HANDLER
HttpStatus 500
HttpReason Internal Server Error
HttpSubStatus 0
ErrorCode The operation completed successfully. (0x0)
ConfigExceptionInfo


Everything (Program.cs, Startup.cs) are set up exactly as in[1]. Behaves exactly the same in IISExpress (VS2015 Update 3) and IIS7.5 (Windows 7). Other return types, 200 (with
new ObjectResult(item)
for GET, 404 for
NotFound()
or 204
NoContentResult()
for PUT work ok. So it seems like it's a problem with 201 somehow. Any ideas?

[1] https://docs.asp.net/en/latest/tutorials/first-web-api.html

update
Posting additional details per request:

Startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FirstWebApi.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace FirstWebApi
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddLogging();
services.AddSingleton<ITodoRepository, TodoRepository>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

app.UseDeveloperExceptionPage();
app.UseStatusCodePages();

app.UseMvc();
}
}
}


TodoController.cs:

using System.Collections.Generic;
using FirstWebApi.Models;
using Microsoft.AspNetCore.Mvc;

namespace FirstWebApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
public TodoController(ITodoRepository todoItems)
{
TodoItems = todoItems;
}
public ITodoRepository TodoItems { get; set; }

public IEnumerable<TodoItem> GetAll()
{
return TodoItems.GetAll();
}

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
var item = TodoItems.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
TodoItems.Add(item);
return CreatedAtAction("GetTodo", new { id = item.Key }, item);
}

[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody] TodoItem item)
{
if (item == null || item.Key != id)
{
return BadRequest();
}

var todo = TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}

TodoItems.Update(item);
return new NoContentResult();
}

[HttpDelete("{id}")]
public void Delete(string id)
{
TodoItems.Remove(id);
}
}
}


update 2

turns out,
CreateAtAction
with 201 tries to redirect to unhandled route:

System.InvalidOperationException: No route matches the supplied values.
at Microsoft.AspNetCore.Mvc.CreatedAtActionResult.OnFormatting(ActionContext context)


Still not sure why tho, as the method
GetById
should be mapped to
GetTodo
per Controller settings.

Answer

So, after a bit of debugging I found what caused it. It turned out that this.Url.Action("Get", "GetTodo", new { id=item.Key }) would return null and the 500 came from the fact that IIS could not match route attached to 201.

As it turns out, You either need to setup:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action}");
});

or use CreateAtRoute.

Comments