CreatedAtAction says the route doesn't exist in .NET 6 - asp.net-core

I'm having issues getting a CreatedAtAction to work properly. In my controller I have these methods:
[HttpGet("{uid:guid}")]
public async Task<IActionResult> GetAsync(Guid uid) => Ok(await _repository.GetAsync(uid));
[HttpPut]
public async Task<IActionResult> StoreAsync([FromQuery] Guid? uid, [FromBody] UpdateInputDTO data)
{
var output = await _repository.StoreAsync(uid, data);
if (uid is null)
return CreatedAtAction(nameof(GetAsync), new {
uid = output.Uid
});
else
return Ok(output);
}
and in my startup I did this:
services.AddControllers(x => x.SuppressAsyncSuffixInActionNames = false);
But when that code runs, I always get an exception saying the route doesn't exist.

Use "Get" instead of "nameof(GetAsync)".
For example, the action name for ProductsController.ListProductsAsync will be canonicalized as ListProducts.. Consequently, it will be routeable at /Products/ListProducts with views looked up at /Views/Products/ListProducts.cshtml.
SuppressAsyncSuffixInActionNames

Related

HTTP Errors When Using POST Methods

I can't quite seem to figure out how to call HTTP POST functions from my Blazor WASM project hosted with ASP.NET. I am having trouble finding any examples of using POST methods past .NET 6 likely because it's so new. I've tried setting content-headers to JSON and many different ways of retrieving the request body from the actual controller function, but I just get 500, 415, and 400 errors. I've also tried not using model binding the the controller function, but to no avail. I do not believe this is the issue though, as using the [ApiController] attribute infers proper model binding as far as I know. I can only imagine the issue stems from the HTTP call.
The service that calls the method:
public async Task CreateUser(User user)
{
await _httpClient.PostAsJsonAsync("users", user);
}
The controller function:
[HttpPost]
public async Task PostUser(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
}
The given from the above code is just a simple 400 error.
Also, I've added a test user into the database manually, and I'm able to retrieve it without any issues.
Here's some code from one of my demo projects showing API calls to get WeatherForecast records.
Here's the Web Assembly project DataBroker:
public class WeatherForecastAPIDataBroker : IWeatherForecastDataBroker
{
private readonly HttpClient? httpClient;
public WeatherForecastAPIDataBroker(HttpClient httpClient)
=> this.httpClient = httpClient!;
public async ValueTask<bool> AddForecastAsync(WeatherForecast record)
{
var response = await this.httpClient!.PostAsJsonAsync<WeatherForecast>($"/api/weatherforecast/add", record);
var result = await response.Content.ReadFromJsonAsync<bool>();
return result;
}
public async ValueTask<bool> DeleteForecastAsync(Guid Id)
{
var response = await this.httpClient!.PostAsJsonAsync<Guid>($"/api/weatherforecast/delete", Id);
var result = await response.Content.ReadFromJsonAsync<bool>();
return result;
}
public async ValueTask<List<WeatherForecast>> GetWeatherForecastsAsync()
{
var list = await this.httpClient!.GetFromJsonAsync<List<WeatherForecast>>($"/api/weatherforecast/list");
return list!;
}
}
And here's the controller it's calling:
namespace Blazr.Demo.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private IWeatherForecastDataBroker weatherForecastDataBroker;
public WeatherForecastController(IWeatherForecastDataBroker weatherForecastDataBroker)
=> this.weatherForecastDataBroker = weatherForecastDataBroker;
[Route("/api/weatherforecast/list")]
[HttpGet]
public async Task<List<WeatherForecast>> GetForecastAsync()
=> await weatherForecastDataBroker.GetWeatherForecastsAsync();
[Route("/api/weatherforecast/add")]
[HttpPost]
public async Task<bool> AddRecordAsync([FromBody] WeatherForecast record)
=> await weatherForecastDataBroker.AddForecastAsync(record);
[Route("/api/weatherforecast/delete")]
[HttpPost]
public async Task<bool> DeleteRecordAsync([FromBody] Guid Id)
=> await weatherForecastDataBroker.DeleteForecastAsync(Id);
}
The Repo for the Demo Project Blazor.Demo
Controller Code
Data Broker Code

route values are being passed after code execute in asp net core?

[HttpGet]
public async Task<IActionResult> AnswerExam(int examId)
{
var obb = onlineExamDBContext.QuestionAndAnswers.Where(x => x.ExamId == examId);
return View(await obb.ToListAsync());
}
<a asp-action="AnswerExam" asp-route-id="#item.ExamId">Attempt</a>
but later after code executes i can see the id being passed in the url
https://localhost:44395/CandidateExam/AnswerExam/16
but tht id 16 is not being passed to the action method?? what cud be the reason? please help!!
[HttpGet]
public async Task<IActionResult> AnswerExam(int examId)
{
var obb = onlineExamDBContext.QuestionAndAnswers.Where(x => x.ExamId == examId);
return View(await obb.ToListAsync());
}
Found the issue. The examId in above action method should match with the route values such tht
asp-route-examId="#item.ExamId"

asp.net core 'No route matches the supplied values' with controller-level route values

The following controller throws a System.InvalidOperationException: No route matches the supplied values exception when posting a new item (by the CreatedAtRoute method):
namespace MyApp.Controllers
{
[ApiController]
[Route("api/offices/{officeId}/[controller]")]
public class ResourcesController : ControllerBase
{
/* ... */
[HttpGet]
public async Task<IActionResult> GetAsync(Guid officeId) =>
this.Ok(await this.client.GetResourcesAsync(officeId));
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}
[HttpPost]
public async Task<IActionResult> PostAsync(Guid officeId, Resource resource)
{
try
{
var result = await this.client.CreateResourceAsync(officeId, resource);
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
}
catch (Exception e)
{
this.logger.LogError(e, "Error while creating a resource");
return this.StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
Is this a bug or is there another way to use route values specified at controller level?
Tested on asp.net core 3 (preview 8).
The Id value of the resource was null, and therefore the anonymous object passed to the method was missing the id value, which is not optional on the route template and therefore the route was not found.
Using result.Id solved it, because the client was assigning the id (generated by the MongoDb instance behind).
I think its because of the parameters that you gave.
In this line you are giving 2 anonymus objects params to your root, but your root contains and asks for 1 parameter.
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
EDIT
I think the {id} parameter makes a conflict over your root parameters.
[Route("api/offices/{officeId}/[controller]")]
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}

CreatedAtRouteResult with swagger attribute

I have asp.net core web api. Where one of my controller returns CreatedAtRouteResult. How can I add swagger attribute for this method.
[HttpPost]
[SwaggerResponse(400, typeof(NotFoundResult))]
[SwaggerResponse(201, typeof(CreatedAtRouteResult))]
public async Task<IActionResult> Create([FromBody] SubscriptionDTO dto)
{
var issuedTo = (await _tokenService.Get()).IssuedTo;
SubscriptionDresult = await _subscriptionService.CreateAsync(dto, issuedTo.Id);
return result == null ? (IActionResult)NotFound(): CreatedAtRoute(new {id = result.Id}, result);
}
Can someone explain how to set the swagger response attribute from such type?
If you use ProduceResponseType attribute on your method, swagger will automatically pick it up and add it to the swagger.json document.
[HttpPost]
[ProducesResponseType(typeof(SubscriptionDTO), (int)HttpStatusCode.Created)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Create([FromBody] SubscriptionDTO dto)
{
var issuedTo = (await _tokenService.Get()).IssuedTo;
SubscriptionDTO result = await _subscriptionService.CreateAsync(dto, issuedTo.Id);
return result == null ? (IActionResult)NotFound(): CreatedAtRoute(new {id = result.Id}, result);
}
Note that you need to specify the actual DTO type returned for each response code in ProduceResponseType. Use void for no return body.

MVC 6 Web Api: Resolving the location header on a 201 (Created)

In Web Api 2.2, we could return the location header URL by returning from controller as follows:
return Created(new Uri(Url.Link("GetClient", new { id = clientId })), clientReponseModel);
Url.Link(..) would resolve the resource URL accordingly based on the controller name GetClient:
In ASP.NET 5 MVC 6's Web Api, Url doesn't exist within the framework but the CreatedResult constructor does have the location parameter:
return new CreatedResult("http://www.myapi.com/api/clients/" + clientId, journeyModel);
How can I resolve this URL this without having to manually supply it, like we did in Web API 2.2?
I didn't realise it, but the CreatedAtAction() method caters for this:
return CreatedAtAction("GetClient", new { id = clientId }, clientReponseModel);
Ensure that your controller derives from MVC's Controller.
In the new ASP.NET MVC Core there is a property Url, which returns an instance of IUrlHelper. You can use it to generate a local URL by using the following:
[HttpPost]
public async Task<IActionResult> Post([FromBody] Person person)
{
_DbContext.People.Add(person);
await _DbContext.SaveChangesAsync();
return Created(Url.RouteUrl(person.Id), person.Id);
}
There is an UrlHelper class which implements IUrlHelper interface.
It provides the requested functionality.
Source code
There is also CreatedAtRoute:
public async Task<IActionResult> PostImpl([FromBody] Entity entity)
{
...
return CreatedAtRoute(entity.Id, entity);
//or
return CreatedAtRoute(new { id = entity.Id }, entity);
}
My GET action has a route name
[HttpGet("{id:int}", Name = "GetOrganizationGroupByIdRoute")]
public async Task<IActionResult> Get(int id, CancellationToken cancellationToken = default(CancellationToken))
{
...
}
And my POST action uses that route name to return the URL
[HttpPost]
public async Task<HttpStatusCodeResult> Post([FromBody]OrganizationGroupInput input, CancellationToken cancellationToken = default(CancellationToken))
{
...
var url = Url.RouteUrl("GetOrganizationGroupByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent());
Context.Response.Headers["Location"] = url;
...
}
Resulting response using Fiddler
Hope that helps.
I use this simple approximation based on the Uri being served at the web server:
[HttpPost]
[Route("")]
public IHttpActionResult AddIntervencion(MyNS.MyType myObject) {
return Created<MyNS.MyType>(Request.RequestUri + "/" + myObject.key, myObject);
}