Multiple get methods in .net core 3.1 web api

Viewed 14038

I am trying to create a very basic controller with three get methods Below are the three uri's for them

  1. GET /movies - gets all movies.
  2. GET /movies?name={name}` - finds all movies matching the specified name
  3. Get /Movies/{Id} - Find movie By Id

My code for the controller is as below

[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
    private readonly IMoviesService moviesService;

    public MoviesController(IMoviesService moviesService)
    {
        this.moviesService = moviesService;
    }
    
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var result = await moviesService.GetMoviesAsync();
        return Ok(result);
    }
    
    [HttpGet]
    public async Task<IActionResult> GetByName([FromQuery(Name = "name")] string name)
    {
        var result = await moviesService.GetMoviesByNameAsync(name);
        return Ok(result);
    }

    [HttpGet("{Id}", Name = "GetById")]
    public async Task<IActionResult> GetById(Guid Id)
    {
        var result = await moviesService.GetMovieById(Id);
        return Ok(result);
    }       

}

When i send the request to GetById by api/movies/31234567-89ab-cdef-0123-456789abcdef then it works but for api/movies and api/movies?name=Test i get below error

The request matched multiple endpoints. Matches: MoviesController.Get and MoviessController.GetByName

Can anyone please suggest me what is the best way to implement such scenario in web api .net core 3.1 considering best practises?

5 Answers

You'd better to change your url like api/Movies/GetByName/{Id},api/Movies/GetById/{Name}.

Then:

  1. GET /movies - gets all movies.
  2. GET /movies/GetByName/{name}` - finds all movies matching the specified name
  3. Get /Movies/GetById/{Id} - Find movie By Id

When add other action ,you can also add action name to route,it can help you avoid The request matched multiple endpoints.

Here is a demo:

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            return Ok();
        }

        [HttpGet("GetByName/{name}")]
        public async Task<IActionResult> GetByName(string name)
        {
            return Ok();
        }

        [HttpGet("GetById/{Id}")]
        public async Task<IActionResult> GetById(Guid Id)
        {
            return Ok();
        } 

result: enter image description here

Your Get and GetByName actions have no attribute route provided for them and are both matched to "api/Movies". In order to resolve that you need to add a route template to at least one of those actions, too.

A possible solution is:

[HttpGet]
public async Task<IActionResult> Get()
{
    var result = await moviesService.GetMoviesAsync();
    return Ok(result);
}

[HttpGet]
[Route("{name}")]
public async Task<IActionResult> GetByName([FromRoute] string name)
{
    var result = await moviesService.GetMoviesByNameAsync(name);
    return Ok(result);
}

Note that here the the url for movies with a specific name is no longer /movies?name={name}, but /movies/{name}.

If you absolutely wish to keep the URL /movies?name={name} you can merge the Get and GetByName actions in a single action that accepts a query string parameter name and based on its value execute your logic.

@Nishant, your is almost good with some minor changes. Below is the working solution. The comments are inline within the code.

[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
    //GET /movies - gets all movies.
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return Ok();
    }

    //GET /movies/name={name}` - finds all movies matching the specified name
    [HttpGet("{name}")]  //added the route
    public async Task<IActionResult> GetByName(string name) // modified the input parameter
    {
        return Ok();
    }

    //Get /Movies/{Id} - Find movie By Id
    [HttpGet("{Id:guid}", Name = "GetById")] //added the input type
    public async Task<IActionResult> GetById(Guid Id)
    {
        return Ok();
    }

}

Please let me know if this helps.

You can have multiple methods, keeping in mind that at the beginning of the controller you initialized the way to call the controller methods, like this:

    [Route("api/[controller]")]

.... each action or method must specify [action] and name

 [Route("[action]", Name = "GetPersona")]
    [HttpGet]
    public async Task<IActionResult> GetPersona(int Id)
    {
        bool ok = false;
        string mensaje = "Sin Datos";
        var data = await Task.Run(() => _repositoryPersonas.GetPersona(Id));
        if (data != null)
        {
            mensaje = "ok";
            ok = true;
        }
        return Ok(new { data, ok, mensaje });
    }


    [Route("[action]", Name = "ObtenerPersonas")]
    public async Task<IActionResult> ObtenerPersonas(int Ids)
    {
        bool ok = false;
        string mensaje = "Sin Datos";
        var data = await Task.Run(() => _repositoryPersonas.GetPersona(Ids));
        if (data != null)
        {
            mensaje = "ok";
            ok = true;
        }
        return Ok(new { data, ok, mensaje });
    }

route1https://localhost:44376/api/Personas/GetPersona?Id=1 route2https://localhost:44376/api/Personas/ObtenerPersonas?Id=1

I use this

[HttpGet("[action]")]
public async Task<IActionResult> GetByName([FromQuery(Name = "name")] string name)
{
    var result = await moviesService.GetMoviesByNameAsync(name);
    return Ok(result);
}
Related