Reputation: 2051
I can do the [HttpGet]
and [HttpGet("{id}")]
requests fine. But when I do [HttpPost]
, I'm running into problems. I'm using postman to validate the http requests. I haven't setup a database yet, and am just using an api with models for now. Here is the code structure...
controller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CoreStudyApi.OutgoingObjects;
using CoreStudyApi.Api;
using CoreStudyApi.IncomingObjects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace CoreStudyApi.Controllers
{
[Route("people")]
[ApiController]
[Produces("application/json")]
public class PeopleController : Controller
{
private People_API P_API = new People_API();
[HttpGet]
public IActionResult Get()
{
var Result = P_API.GetPeople().ToList(); // Cannot pass Dictionary over httprequest, must convert to list or string.
//var Result = "test";
/*var Result = new List<string>()
{
"one",
"two",
"three"
};*/
return Ok(Result);
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult Get(int Id)
{
try
{
var Result = P_API.GetPersonNameById(Id);
return Ok(Result);
} catch(Exception e)
{
return NotFound(e.Message);
// return NotFound("User not found");
// return NotFound();
}
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Person> Post([FromBody] PersonName NameObject)
{
string Name = NameObject.Name;
if(string.IsNullOrWhiteSpace(Name))
{
return BadRequest();
}
Person NewPerson = P_API.AddPerson(Name);
return CreatedAtAction("Created", new { id = NewPerson.Id, name = NewPerson.Name }, NewPerson);
}
}
}
I can see the Person object was created successfully (using visual studio debugger) and has the values of an Id to 6 and a Name to "scrappy", but the problem hits when I try to do the return.
People_API.cs
using CoreStudyApi.OutgoingObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CoreStudyApi.Models;
namespace CoreStudyApi.Api
{
public class People_API
{
// Variables
private People P_Model = new People();
// Helper Functions
private int GetNextUnusedPeopleId()
{
for(int NewId = 1; NewId < P_Model.PeopleRecords.Count(); NewId++)
{
if (!P_Model.PeopleRecords.ContainsKey(NewId))
{
return NewId;
}
}
return P_Model.PeopleRecords.Count() + 1;
}
// Private Functions
private Dictionary<int, string> GetPeopleFromData()
{
return P_Model.PeopleRecords;
}
private string GetPersonNameByIdFromData(int Id)
{
if (!P_Model.PeopleRecords.ContainsKey(Id))
{
return "Not Found";
}
return P_Model.PeopleRecords[Id];
}
private Person AddPersonToData(string Name)
{
// Get the next available Id
int NewId = GetNextUnusedPeopleId();
// Add the new person to the data
P_Model.PeopleRecords.Add(NewId, Name);
// Check to see if the new person was added and store as an object
Person NewPerson = new Person(NewId, P_Model.PeopleRecords[NewId]);
// Return the person added in the form of a person object
return NewPerson;
}
// Public Functions
public Dictionary<int, string> GetPeople()
{
return GetPeopleFromData();
}
public string GetPersonNameById(int Id)
{
return GetPersonNameByIdFromData(Id);
}
public Person AddPerson(string Name)
{
return AddPersonToData(Name);
}
}
}
PersonName.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CoreStudyApi.IncomingObjects
{
public class PersonName
{
public string Name { get; set; }
}
}
People.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CoreStudyApi.Models
{
public class People
{
public Dictionary<int, string> PeopleRecords = new Dictionary<int, string>()
{
{ 1, "shaggy" },
{ 2, "fred" },
{ 3, "velma" },
{ 4, "daphne" },
{ 5, "scooby" }
};
}
}
Person.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace CoreStudyApi.OutgoingObjects
{
public class Person
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
// Constructor
public Person(int Id, string Name)
{
this.Id = Id;
this.Name = Name;
}
}
}
The problem code snippet...
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Person> Post([FromBody] PersonName NameObject)
{
string Name = NameObject.Name;
if(string.IsNullOrWhiteSpace(Name))
{
return BadRequest();
}
Person NewPerson = P_API.AddPerson(Name); // Debugger confirms object was created successfully
// Fails to return here
return CreatedAtAction("Created", new { id = NewPerson.Id, name = NewPerson.Name }, NewPerson);
}
Receives the following with postman:
Updated Per Request:
Postman Output
System.InvalidOperationException: No route matches the supplied values.
at Microsoft.AspNetCore.Mvc.CreatedAtActionResult.OnFormatting(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncCore(ActionContext context, ObjectResult result, Type objectType, Object value)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
at Microsoft.AspNetCore.Mvc.ObjectResult.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultAsync(IActionResult result)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Accept-Encoding: gzip, deflate
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 22
Content-Type: application/json
Host: localhost:44325
User-Agent: PostmanRuntime/7.21.0
Postman-Token: 33b94ca5-5a81-451c-9221-380d27a17536
Upvotes: 2
Views: 2632
Reputation: 59
Return the Person object directly instead of returning CreatedAtAction.
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Person> Post([FromBody] PersonName NameObject)
{
string Name = NameObject.Name;
if (string.IsNullOrWhiteSpace(Name))
{
return BadRequest();
}
Person NewPerson = P_API.AddPerson(Name); // Debugger confirms object was created successfully
// Fails to return here
return NewPerson; // CreatedAtAction("Created", new { id = NewPerson.Id, name = NewPerson.Name }, NewPerson);
}
Upvotes: 1
Reputation: 7803
It looks to me like the issue you are experiencing occurs after the POST has executed.
Here is the signature for the method you use to return, according to the documentation:
[Microsoft.AspNetCore.Mvc.NonAction]
public virtual Microsoft.AspNetCore.Mvc.CreatedAtActionResult CreatedAtAction (string actionName, object routeValues, object value);
The method creates an instance of type CreatedAtActionResult
which returns a response with a Location
header constructed using the parameters you pass. For 201 responses, this header represents the URL to the newly created resource (where you can find it).
I don't see an Action method called Created
in your code. I think it's trying to construct the Url using "created"
and new { id = NewPerson.Id, name = NewPerson.Name }
and it can't find a matching route, so it fails.
Change the return line to this:
return CreatedAtAction("Get", new { id = NewPerson.Id }, NewPerson);
This should result in a route that matches the existing method public IActionResult Get(int Id)
.
Upvotes: 3