Why is my attribute routing not working?
Asked Answered
B

2

6

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?

Bailsman answered 29/10, 2017 at 6:55 Comment(4)
mvc-5? - looks like asp.net-core-mvcNadiya
@StephenMuecke It is Core, my mistake. I often mix up MVC 5 and 6.Bailsman
Is this the entire interface of ClientsController? The 3 action methods in the exception message are not in the code example. If all are posted, we will need to see the rest of your routing configuration to determine which route is matching the request for api/Clients/Get.Rostov
@Rostov This is the entire code of the only controller. In the API. Turns out those error messages were from a controller in the client project.Bailsman
R
8

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
}
Recalcitrate answered 29/10, 2017 at 10:38 Comment(0)
L
0

I Think there is a typo in the route, it should be Clients with an (s) at the end instead of Client.

[HttpGet("api/Clients/Get")]

instead of

[HttpGet("api/Client/Get")]

Or change the call to the endpoint to :

 json = await Client.GetStringAsync("api/Client/Get");
Linkman answered 29/10, 2017 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.