7

I have the following interface in my business layer

    public interface IUserService
    {
        void CreateUser(User user);
        List<User> FindUsersByName(string searchedString);
        User GetUserById(int userId);
        User GetUserByCredentials(string login, string password);
        void UpdateUser(User user);
        void UpdateUserPassword(int userId, string oldPassword, string newPassword);
    }

Now I want to provide web API for this interface. As you can see this interface has multiple get methods that return one item GetUserById and GetUserByCredentials, it also has multiple update methods UpdateUser and UpdateUserPassword, in future I might want to add additional get method that returns a collection, like, GetAllUsers, for instance.

The obvious solution was to encapsulate this functionality in one controller. So what I did first, in WebApiConfig I changed routes configuration to

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                //as you can see I added {action} to the path so that, it will be possible to differentiate between different get/put requests
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ); 

Then I created a UsersController that looks like this

public class UsersController : ApiController
    {
        private readonly IUserService _userService;

        public UsersController(IUserService userService)
        {
            _userService = userService;
        }

        // POST api/users/createuser
        [HttpPost]
        public IHttpActionResult CreateUser(User user)
        {
            //some code
        }

        // GET api/users/getuserbyid?id=1
        [HttpGet]
        public IHttpActionResult GetUserById(int id)
        {
            //some code
        }

        // GET api/users/getuserbycredentials?login=log&password=pass
        [HttpGet]
        public IHttpActionResult GetUserByCredentials(string login, string password)
        {
            //some code
        }


        // GET api/users/findusersbyname?searchedString=jack
        [HttpGet]
        public IHttpActionResult FindUsersByName(string searchedString)
        {
            //some code
        }

        // PUT api/users/updateuser
        [HttpPut]
        public IHttpActionResult UpdateUser(UserBase user)
        {
            //some code
        }

        // PUT api/users/updateuserpassword?userId=1&oldPassword=123&newPassword=1234
        [HttpPut]
        public IHttpActionResult UpdateUserPassword(int userId, string oldPassword, string newPassword)
        {
            //some code
        }
    }

As you can see from the code above I have different URIs for each action method, e.g., for GetUserById - api/users/getuserbyid?id=1, for GetUserByCredentials - api/users/getuserbycredentials?login=log&password=pass and so on. This solution works fine so far, but the problem is, as far as I know you cannot have multiple gets according to REST, so does this solution still comply with the constraints for a RESTful service? And if not how can I make it truly RESTful? The idea of splitting this interface into different controllers seems a little odd to me, because in the future I may want to add some new methods to my interface, like, GetUsersByGender, GetUsersByDateOfBirthday and so on (if I'm going to create a new controller each time, that doesn't sound right to me)

abatishchev
  • 111
  • 7
  • looks good to me – Ewan May 16 '17 at 18:26
  • Where did you read that having more than one GET is not RESTful? – Laiv May 16 '17 at 19:18
  • @Laiv ,one of my colleagues told me that a good restful api should have one get by id, one get for a collection and single methods for put, post, delete.So basically if you have user service you should have `api/users` for all users` api/users/id` for one user and `api/users` for post put and delete accordingly. But having something like `api/users/getuserbyid?id=1` and `api/users/getuserbycredentials?login=log&password=pass` is not restful, because the client is then bound to specific verbs, i.e., `getuserbyid` and `getuserbycredentials` – Mykhailo Seniutovych May 16 '17 at 19:51

3 Answers3

8

I know you cannot have multiple gets according to REST

Not really. REST and API modelling are different subjects. REST APIs are meant to be an integration strategy, based on the premises introduced by Fielding on his dissertation about distributed architectural styles. These premises have nothing to do with how APIs are modelled, how many resources, URIs and semantics we provide.

For instance:

/api/users-living-in-courscant
/api/users-not-living-in-courscant
/api/users?q=living:coruscant
/api/users?q=id:12345
/api/user/12345
/api/me

Some of the above URIs might refer to the same resource(s), the main difference (and key point) lays on their respective semantics.

so does this solution still comply with the constraints for a RESTful service?

In my opinion, your approach is closer to an RPC-like web service than an API REST. Take a look at Martin Fowler's article about Richardson Maturity Model.

If we read Martin's post carefully, we find that Martin is not introducing API modelling techniques or best practices. He sets the focus on how to make the communication client-server properly according to the HTTP semantics. How to represent and discover resources.

But, he does not mention how to identify these resources. He does not mention how to shape URIs.

And if not how can I make it truly RESTful?

If making the API totally RESTful is the concern, I would suggest reading Fielding dissertation first. Once assimilated the meaning of REST, I would look for documentation related to API modelling. As soon as the last agreed with the former you should be on the good path.

Here 2 links to start working:

The above links follow a deliberated order. I think it's a natural order that goes from basics to advanced concepts. From the ground up.

I have emphasised the word conventions intendedly. These can be dismissed or interpreted if you consider them to be inadequate to your needs.


Further readings

If you are interested in the topic, I have found the following books to be insightful.

  • API Design Rulebook: It is mostly focused on the API modelling. You will find a brief introduction to the web architecture and to the REST principles.

  • Rest in practice: I consider this one to be more advanced. Rather focused on the advantages of REST as an integration than on API modelling.

Laiv
  • 14,283
  • 1
  • 31
  • 69
  • I see a typo in "Note that Martin neither do a single reference to" and don't know what it should be instead. – Bernard Vander Beken Nov 28 '18 at 12:47
  • 1
    @BernardVanderBeken thank you for the comment. Sadly, I'm not as good in English as I would like to be. I have edited the whole paragraph. My intention is to remark the fact that Martin does not speak about API modelling in his post when it comes to make our web APIs restful. That's why API modelling and making the API to be Restful are different matters. – Laiv Nov 28 '18 at 14:41
4

as far as I know you cannot have multiple gets according to REST

No, not really. What you can't have is state. For instance, if you have an API such as:

POST /set-current-user/[id]
GET /user-info
GET /user-avatar
POST /change-password

which means that in order to get the profile picture of the user, you should first call set-current-user, you're not RESTful.

Aside that, you're free to have as many GET or non-GET actions in your controller as needed, since among six architectural constraints, there is no constraint which tells you not to have more than one action per controller.


Also, I can't avoid highlighting one of your examples:

GET api/users/getuserbycredentials?login=log&password=pass

If this is the actual route you've used, don't. Even with HTTPS (and you have to use HTTPS, since you're manipulating sensitive user data here), this has a huge security flaw of sending passwords plain text right into the server logs. Even if you're absolutely sure that the logs are encrypted and the traffic from the application server to the logs is done with a secure protocol, the sole fact of storing users' passwords plain text is terrible in terms of security. Don't do that. Never.

Arseni Mourzenko
  • 134,780
  • 31
  • 343
  • 513
  • 1
    Just to expand on your highlight, if you need your client to send a user password for verification then make them send it in the body of a POST request. Request bodies are generally not kept in log files. – bdsl May 16 '17 at 21:52
  • Just to expand on your highlight, if you need your client to send a user password for verification then make them send it in the body of a POST request. Request bodies are generally not kept in log files. – bdsl May 16 '17 at 21:52
  • Thanks for an answer, talking about security, what would be a good solution for `api/users/getuserbycredentials?login=log&password=pass`? Put this part into the body `login=log&password=pass` , and use https will suffice? – Mykhailo Seniutovych May 17 '17 at 05:09
  • I would use POST and of course HTTPS. If I think a plus in security is needed I would even encode the password. – Laiv May 17 '17 at 06:56
  • @Laiv, this method gets the resource from the database by credentials, rather than creates one, is post a good way to do this? – Mykhailo Seniutovych May 17 '17 at 10:29
  • For logins? Yes. The authentication process is like a procedure call (RPC) rather tan a REST resource. – Laiv May 17 '17 at 10:52
  • @MykhailoSeniutovych: `Authorization` HTTP header seem wrong in your case (the goal is not to authenticate the request itself, but to check for users credentials). So as suggested in other comments, pass the credentials in POST request itself. – Arseni Mourzenko May 17 '17 at 16:35
4

REST does not limit the number of methods. You can use as many of them as you need. The API and your implementation are 2 different things. If you want your API to follow the REST architecture then it it is about exchanging resources. How you handle in your code is up to you.

Your code deals with users. But the URI's look like actions, not resources. Consider a schema like this:

GET  /users      - returns the collection of users
GET  /users/{id} - return the user with a specific ID
POST /users      - add a user to the collection

searching based on criteria could look like this:
GET  /users?country={country-code}&city={city} 

This exposes the users a resources and uses the methods of the HTTP protocol to identify the action to perform on these resources.

People have different ideas about what makes an API a REST API. Most of the API's I've read about or seen in examples are limited. They are not exposed at web scale with a great number of client implementations.

Also the use of explicit MIME types to identify the transferred resources and a structured way to define links between resources is often not included.

So, think what you want your API to be, a 'real' REST API, or something simpler that uses logical URI's, HTTP methods and HTTP response codes to communicate.

Kwebble
  • 345
  • 1
  • 10