1

I'm trying to get a REST service up and running (I followed this tutorial), and was trying to extend it with a simple method to mark one of the ToDoItem as "Complete"; literally to pass an ID into a method which should mark it as "Complete".

However, I'm struggling to understand how the routing works.

This is the method provided by default, which works correctly via https://localhost:44388/api/values

If I add another GET operation, even with different [Route] attribute, then I end up with "AmbiguousActionException: Multiple actions matched"

[Route("api/values")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

I tried to specify a route prefix using the method below, so that I could add doesn't work; I get a 404 on https://localhost:44388/api/values and https://localhost:44388/api/values/getbyname

[RoutePrefix("api/values")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [Route("getbyname")]
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

I might be trying the wrong method, so I'm happy to take any advice. I just want to be able to create new REST calls and have them the appropriate actions. Do I need to create other controllers? Am I limited to one GET/POST/PUT etc per controller?


Edit: didn't provide enough info, here's more code:

[Route("api/ToDo")]
[ApiController]
public class ToDoController : ControllerBase
{
    private readonly ToDoContext _context;

    public ToDoController(ToDoContext toDoContext)
    {
        _context = toDoContext;

        if (_context.ToDoItems.Count() == 0)
        {
            //collection is empty, so add a new item
            ToDoItem item1 = new ToDoItem(1, "example 1");
            ToDoItem item2 = new ToDoItem(2, "example 2");

            _context.ToDoItems.Add(item1);
            _context.ToDoItems.Add(item2);
            _context.SaveChanges();
        }
    }

    //GET: api/todo
    [HttpGet]
    public async Task<ActionResult<IEnumerable<ToDoItem>>> GetToDoItems()
    {
        return await _context.ToDoItems.ToListAsync();
    }

    //GET: api/todo/5
    //[HttpGet(Name = "Get a ToDoItem")]
    //[Route("get{id}")]
    [HttpGet("{id}")]
    public async Task<ActionResult<ToDoItem>> GetToDoItem(long id)
    {
        var todoitem = await _context.ToDoItems.FindAsync(id);

        if (todoitem == null)
        {
            return NotFound();
        }
        return todoitem;
    }

    //POST: api/Todo
    [HttpPost]
    public async Task<ActionResult<ToDoItem>> PostToDoItem(ToDoItem todoItem)
    {
        _context.ToDoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        //calls the "GetToDoItem" method above!
        return CreatedAtAction("GetToDoItem", new { id = todoItem.ID }, todoItem);

    }

    //DELETE: api/todo/5
    [HttpDelete("{id}")]
    public async Task<ActionResult<ToDoItem>> DeleteToDoItem(long id)
    {
        var todoItem = await _context.ToDoItems.FindAsync(id);
        if(todoItem == null)
        {
            return NotFound();
        }

        _context.ToDoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return todoItem;
    }



    //* -. space to create a "MarkAsComplete" method
    //GET: api/todo/5
    [HttpGet(Name = "{name}")]        
    public async Task<ActionResult<ToDoItem>> MarkAsComplete(long id)
    {
        var todoitem = await _context.ToDoItems.FindAsync(id);

        if (todoitem == null)
        {
            return NotFound();
        }
        else
        {
            todoitem.IsComplete = true;
        }
        return todoitem;
    }
    //*/
}

1 Answer 1

2

Mixing up different versions of the attributes. RoutePrefix is from a previous version.

Routes need to be unique per action to avoid route conflicts.

For example.

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase {
    // GET api/values        
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get() {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/some_name 
    [HttpGet("{name}")]
    public IActionResult GetByName(string name) {
        return Ok();
    }
}

Reference Routing to controller actions in ASP.NET Core

When building a REST API, it's rare that you will want to use [Route(...)] on an action method. It's better to use the more specific Http*Verb*Attributes to be precise about what your API supports. Clients of REST APIs are expected to know what paths and HTTP verbs map to specific logical operations.

Based on the additional details provided, that MarkAsComplete action should use HTTP PUT so signify that the model is being edited/updated.

For example

//* -. space to create a "MarkAsComplete" method
//PUT: api/todo/5
[HttpPut("{id:long}")]        
public async Task<ActionResult<ToDoItem>> MarkAsComplete(long id) {
    var todoitem = await _context.ToDoItems.FindAsync(id);
    if (todoitem == null) {
        return NotFound();
    } else {
        todoitem.IsComplete = true;
    }
    return todoitem;
}
//*/
Sign up to request clarification or add additional context in comments.

5 Comments

OK, thanks for that. As per the question, I would like to be able to have two different methods which do different things, both of which only need to be assigned an ID. 'GetByID' and 'MarkComplete'. What should I do in that case?
@Sion update the question with that example so I can get a better understanding of what you mean. Are they both GET requests? that sesond one sounds more like a post.
I've updated, thanks for your help and sorry if I didn't provide enough info up front
@Sion Based on the additional details provided, that MarkAsComplete action should use HTTP PUT so signify that the model is being edited/updated. check updated answer.
Thanks for this. What URL do I need to navigate to? api/todo/getbyname/1 doesn't work but localhost:44388/api/todo/1 does (to get the data), is there a way to specify the difference between "GetByID" or "MarkAsComplete"?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.