1

I created a simple Web API with ASP.NET Core. I have the following API:

  • GET /api/messages - get all messages
  • GET /api/messages/{id} - get a message by id
  • POST /api/messages - add a new message
  • PUT /api/messages/{id} - update an existing message
  • DELETE /api/messages/{id} - delete a message

Now, I want another API to get all messages by message owner's name.

What I tried:

I tried to create this API, but it doesn't work because it conflicts with GET /api/messages/{id}:

  • GET /api/messages/{name} <- (doesn't work due to conflicting API)

    // GET: api/messages/{name}
    [HttpGet("{name}")]
    public IEnumerable<Message> GetMessagesByName(string name)
    {
        return _repository.GetMessages().Where(m => m.Owner == name);
    }
    

Here is my Message model Message.cs:

public class Message
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }
    public string Owner { get; set; }
    public string Text { get; set; }
}

Here is my Messages controller MessagesController.cs:

[Route("api/[controller]")]
public class MessagesController : Controller
{
    private readonly IMessageRepository _repository;

    public MessagesController(IMessageRepository repository)
    {
        _repository = repository;
    }

    // GET: api/messages
    [HttpGet]
    public IEnumerable<Message> Get()
    {
        return _repository.GetMessages();
    }

    // GET api/messages/{id}
    [HttpGet("{id}", Name = "GetMessage")]
    public IActionResult GetById(long id)
    {
        var message = _repository.GetMessage(id);
        if (message == null)
        {
            return NotFound();
        }
        return new ObjectResult(message);
    }

    // POST api/messages
    [HttpPost]
    public IActionResult Post([FromBody]Message message)
    {
        if (message == null)
        {
            return BadRequest();
        }

        _repository.AddMessage(message);

        return CreatedAtRoute("GetMessage", new { id = message.Id }, message);

    }

    // PUT api/messages/{id}
    [HttpPut("{id}")]
    public IActionResult Put(long id, [FromBody]Message message)
    {
        if (message == null || message.Id != id)
        {
            return BadRequest();
        }

        var messageToUpdate = _repository.GetMessage(id);
        if (messageToUpdate == null)
        {
            return NotFound();
        }

        messageToUpdate.Owner = message.Owner;
        messageToUpdate.Text = message.Text;

        _repository.UpdateMessage(messageToUpdate);
        return new NoContentResult();
    }

    // DELETE api/messages/{id}
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
        var message = _repository.GetMessage(id);
        if (message == null)
        {
            return NotFound();
        }
        _repository.RemoveMessage(id);
        return new NoContentResult();
    }
}

Question:

How can I create an API method to get all messages by message owner's name?

Ideally, I would like the API to look like GET /api/messages/{name}, but don't think its possible since it conflicts with GET /api/messages/{id}.

I'm thinking of creating the API like this, but I'm not sure how.

  • GET /api/messages/name/{name} <- (or something along that line)

Solution:

To have GET /api/messages/{name} working without conflicting with GET /api/messages/{id}, change attribute [HttpGet("{id}", Name="GetMessage")] to [HttpGet("{id:long}", Name="GetMessage")] for public IActionResult GetById(long id) method.

To also have GET /api/messages/name/{name} working, add [Route("name/{name}")] attribute to public IEnumerable<Message> GetMessagesByName(string name) method.

1
  • An option would be /api/users/{id}/messages or /api/messages?owner=... Commented May 21, 2017 at 21:48

1 Answer 1

2

you can put parameter type in route, so your code method should be look like that:

 // GET api/messages/{id}
    [HttpGet("{id:long}", Name = "GetMessage")]
    public IActionResult GetById(long id)
    {
        var message = _repository.GetMessage(id);
        if (message == null)
        {
            return NotFound();
        }
        return new ObjectResult(message);
    }

I think, web api is ignoring parameters types in routes if they are not typed explicitly, so in your example it has two routes like this: api/messages/{object} and when you put explicit type, they are like this: api/messages/{object} and api/messages/{long}

Sign up to request clarification or add additional context in comments.

4 Comments

I'm now getting the following exception System.InvalidOperationException: The constraint entry 'name' - 'string' on the route 'api/Messages/{name:string}' could not be resolved by the constraint resolver of type 'DefaultInlineConstraintResolver'.
my fault - try to put explicit type only in GetById method
This is because attribute routing has not string constraint as your exception showed, but long is perfectly acceptable. I have updated answer too.
Thanks for providing the solution I was looking for and also mentioning attribute routing.

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.