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)
3 Answers 3
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:
Resource naming conventions Quite basic. Some of the conventions introduced here are broadly accepted as "good practices".
API modelling Here, we delve into the API modelling.
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.
-
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– Bernard Vander Beken11/28/2018 12:47:02Commented Nov 28, 2018 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– Laiv11/28/2018 14:41:41Commented Nov 28, 2018 at 14:41
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.
-
1Just 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– bdsl05/16/2017 21:52:32Commented May 16, 2017 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– bdsl05/16/2017 21:52:49Commented May 16, 2017 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 bodylogin=log&password=pass
, and use https will suffice?Mykhailo Seniutovych– Mykhailo Seniutovych05/17/2017 05:09:15Commented May 17, 2017 at 5: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– Laiv05/17/2017 06:56:28Commented May 17, 2017 at 6: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– Mykhailo Seniutovych05/17/2017 10:29:25Commented May 17, 2017 at 10:29
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.
-
1Well, consider not sending the user credentials through the query string...Laiv– Laiv05/16/2017 22:25:31Commented May 16, 2017 at 22:25
-
I just added that to show how search could be expressed.Kwebble– Kwebble05/17/2017 06:51:59Commented May 17, 2017 at 6:51
-
For authentication HTTP has its own mechanisms.Kwebble– Kwebble05/17/2017 06:56:00Commented May 17, 2017 at 6:56
-
However, the example above for filtering users by login and password may lead to wrong assumptions like the ones is the OP asking in other comments. So I would encourage you to change the password filter by any other field.Laiv– Laiv05/17/2017 11:15:31Commented May 17, 2017 at 11:15
-
1
api/users
for all users` api/users/id` for one user andapi/users
for post put and delete accordingly. But having something likeapi/users/getuserbyid?id=1
andapi/users/getuserbycredentials?login=log&password=pass
is not restful, because the client is then bound to specific verbs, i.e.,getuserbyid
andgetuserbycredentials