14

I am trying to design a RESTful web API for our service using ASP.NET Web API. I'm running into trouble with figuring out how to route non-CRUD actions to the proper controller action. Let's assume my resource is a door. I can do all of the familiar CRUD things with my door. Let's say that model for my door is:

public class Door
{
   public long Id { get; set; }
   public string InsideRoomName { get; set; }
   public string OutsideRoomName { get; set; }
}

I can do all of my standard CRUD operations via my web api:

POST: http://api.contoso.com/v1/doors
GET: http://api.contoso.com/v1/doors
GET: http://api.contoso.com/v1/doors/1234
GET: http://api.contoso.com/v1/doors?InsideRoomName=Cafeteria
PUT: http://api.contoso.com/v1/doors/1234
DELETE: http://api.contoso.com/v1/doors/1234

and so on. Where I run into trouble is when I need to model the non-CRUD actions against my door. I want to model a Lock and Unlock verb against my resource. Reading through the ASP.NET articles the guidance seems to be to switch to an RPC style call when using custom actions. This gives me a path:

PUT: http://api.contoso.com/v1/doors/1234/lock
PUT: http://api.contoso.com/v1/doors/1234/unlock

This seems to conflict with the spirit of REST which aims for the path to indicate a resource. I suppose I could model the verb as a resource:

POST: http://api.contoso.com/v1/doors/1234/lockrequests
POST: http://api.contoso.com/v1/doors/1234/unlockrequests

In this case I could still use the recommend {controller}/{id}/{action} but it seems like I'm still creating a mixed RPC / REST API. Is it possible, or even recommended as far as REST interfaces go, to put the custom action in the list of parameters?

PUT: http://api.contoso.com/v1/doors/1234?lock
PUT: http://api.contoso.com/v1/doors/1234?unlock

I could foresee a need to have this call supported with query parameters as well, such as:

PUT: http://api.contoso.com/v1/doors?lock&InsideRoomName=Cafeteria

How would I create the route to map this request to my DoorsController?

public class DoorsController : ApiController
{
   public IEnumerable<Doord> Get();
   public Door Get(long id);
   public void Put(long id, Door door);
   public void Post(Door door);
   public void Delete(long id);

   public void Lock(long id);
   public void Unlock(long id);
   public void Lock(string InsideRoomName);
}

I may be making some false assumptions here regarding what is and is not best practices with respect to REST API design, so any guidance there is appreciated as well.

JadeMason
  • 1,181
  • 1
  • 14
  • 23
  • Google use a REST API with Blogger, ant it use actions in REST! https://developers.google.com/blogger/docs/3.0/reference/posts/publish – padibro Sep 22 '15 at 07:47

3 Answers3

8

From RESTful principle, maybe it's best to introduce a 'status' property to manage those non-CURD actions. But I don't think it meets the real production development.

Every answer to this kind of question, looks like you must have to use a work-around to enforce your API design meets RESTful. But my concern is, is that really making convenience to both user and developer?

let's take a look on the API3.0 design of Google bloger: https://developers.google.com/blogger/docs/3.0/reference, it's using lot URL for non-CURD actions.

And this is interesting,

POST  /blogs/blogId/posts/postId/comments/commentId/spam

and the description is

Marks a comment as spam. This will set the status of the comment to spam, and hide it in the default comment rendering.

You can see, a comment has a status to indicate whether it's a spam or not, but it was not designed like the answer mentioned above by JoannaTurban.

I think from user point of view, it's more convenient. Don't need to care the structure and the enum value of the "status". And actually you can put lot of attributes into the definition of "status", like "isItSpam", "isItReplied", "isItPublic" etc. The design will becomes unfriendly if the status has many things.

On some business logic requirement, to use an easy to understand verb, instead of trying to make it completely a "real" RESTful, it's more productive, for both user and developer. This is my opinion.

Fan
  • 410
  • 5
  • 10
6

To handle the lock/unlock scenario you could consider adding a State property to the Door object:

   public State State { get; set; }

where State is an enum of available values, e.g.

{
LockedFromOutsideRoom,
LockedFromInsideRoom,
Open
}

To clarify: That you're adding a state to the object is not against restful principles as the state is passed over the api every time you make a call to do something with the Door.

Then via the api you would send a PUT/POST request to change the state of the Door on each lock/unlock. Post would probably be better as it's only one property that gets updated:

POST: http://api.contoso.com/v1/doors/1234/state
body: {"State":"LockedFromInsideRoom"}
Joanna Derks
  • 4,033
  • 3
  • 26
  • 32
  • Would I need to register a custom route for this in WebApiConfig.cs? It would seem this still follows the pattern of {controller}/{id}/{action} where my controller will now have a DoorsController::State(long id, State state); method. – JadeMason Apr 18 '13 at 20:18
  • 1
    @JoannaTurban nice answer! I would even do it with PUT, like this: `PUT: http://api.contoso.com/v1/doors/1234 body: {"State":"LockedFromInsideRoom"}` because, you actually *update* the state. Also, feels more consistent with PUT's [idempotence](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods_and_web_applications): Every time this URL is requested produces exactly the same result. – zafeiris.m Apr 18 '13 at 22:51
  • 1
    The danger of this approach is you can end up creating lots of fine grained resources that are interrelated, going against the coarse grained nature of HTTP and making cache invalidation much more difficult. However, it can be viable option for some scenarios. – Darrel Miller Apr 19 '13 at 14:45
  • I like the solution proposed to insert a state as a property of door resource that I can set by POST (or modify by PUT????) instead of use an action setState – padibro Sep 22 '15 at 06:50
  • Google use a REST API with Blogger, ant it use actions in REST! https://developers.google.com/blogger/docs/3.0/reference/posts/publish – padibro Sep 22 '15 at 07:46
  • Google use a REST API with Blogger, ant it use actions in REST! https://developers.google.com/blogger/docs/3.0/reference/posts/publish – padibro Sep 22 '15 at 07:47
1

From a REST perspective you probably want to be treating the lock as a resource in and of itself. This way you create and delete the lock independently of the door (although presumably locate the lock endpoint from the door representation). The URL of the resource is probably going to be related to the URL of the door, however from a RESTful perspective this is irrelevant. REST is about relationships between resources, so the important part is that the url of the lock is discoverable from the representation of the door.

Steve
  • 1,215
  • 6
  • 11