This is what my controller looks like:
[Route("api/[controller]")]
[Produces("application/json")]
public class ClientsController : Controller
{
private readonly IDataService _clients;
public ClientsController(IDataService dataService)
{
_clients = dataService;
}
[HttpPost]
public int Post([Bind("GivenName,FamilyName,GenderId,DateOfBirth,Id")] Client model)
{
// NB Implement.
return 0;
}
[HttpGet("api/Client/Get")]
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get()
{
var clients = await _clients.ReadAsync();
return Ok(clients);
}
[HttpGet("api/Client/Get/{id:int}")]
[Produces(typeof(Client))]
public async Task<IActionResult> Get(int id)
{
var client = await _clients.ReadAsync(id);
if (client == null)
{
return NotFound();
}
return Ok(client);
}
[HttpGet("api/Client/Put")]
public void Put(int id, [FromBody]string value)
{
}
[HttpGet("api/Client/Delete/{id:int}")]
public void Delete(int id)
{
}
}
Yet when I request the URL /api/Clients/Get
, as follows:
json = await Client.GetStringAsync("api/Clients/Get");
I get back the following exception:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Assessment.Web.Controllers.ClientsController.Index (Assessment.Web) Assessment.Web.Controllers.ClientsController.Details (Assessment.Web) Assessment.Web.Controllers.ClientsController.Create (Assessment.Web)
Client
is an HttpClient
. Please note that no GET action matched the route data, despite the same name, id
.
What could be wrong here?
We can use Convention based Routing and Attribute Routing in the same project. Be sure attribute routing should be defined first to convention based routing.
In short, Convention Routing approaches Routing from the general case; you generally add some routes that will match all or most of your URLs, then add more specific routes for more specialized cases. The other way to approach this problem is via Attribute Routing.
Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources. The earlier style of routing, called convention-based routing, is still fully supported. In fact, you can combine both techniques in the same project.
You are using the attributes wrong.
You have a route on the controller
[Route("api/[controller]")]
which would map to api/Clients
and prefix that to any action routes in the controller.
So that means that
[HttpGet("api/Client/Get")] // Matches GET api/Clients/api/Client/Get
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get()
{
var clients = await _clients.ReadAsync();
return Ok(clients);
}
Matches a GET to api/Clients/api/Client/Get
because of the route prefix on the controller.
Referencing Routing to Controller Actions
You need to update the attribute routes on the actions accordingly
[Route("api/[controller]")]
[Produces("application/json")]
public class ClientsController : Controller {
private readonly IDataService _clients;
public ClientsController(IDataService dataService)
{
_clients = dataService;
}
[HttpPost] //Matches POST api/Clients
public int Post([Bind("GivenName,FamilyName,GenderId,DateOfBirth,Id")] Client model) {
// NB Implement.
return 0;
}
[HttpGet("Get")] //Matches GET api/Clients/Get
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get() {
//...code removed for brevity
}
[HttpGet("Get/{id:int}")] //Matches GET api/Clients/Get/5
[Produces(typeof(Client))]
public async Task<IActionResult> Get(int id) {
//...code removed for brevity
}
[HttpGet("Put")] //Matches PUT api/Clients/Put
public void Put(int id, [FromBody]string value) {
//...code removed for brevity
}
[HttpGet("Delete/{id:int}")] //Matches GET api/Clients/Delete/5
public void Delete(int id) {
}
}
The delete action should actually be refactored to a HTTP DELETE and should return a IActionResult
[HttpDelete("Delete/{id:int}")] //Matches DELETE api/Clients/Delete/5
public IActionResult Delete(int id) {
//...code removed for brevity
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With