Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my attribute routing not working?

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?

like image 777
ProfK Avatar asked Oct 29 '17 06:10

ProfK


People also ask

Is conventional routing and attribute routing can work together?

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.

What is difference between conventional and attribute 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.

Why do we need attribute routing in Web API?

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.


1 Answers

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
}
like image 86
Nkosi Avatar answered Sep 21 '22 19:09

Nkosi