Web API seems to focus mostly on CRUD

Topics: ASP.NET Web API
Apr 25, 2012 at 4:40 AM

There doesn't seem to be support for operations beyond CRUD.

I am building an API for a provisioning application, and our entities are quite sophisticated, thus I'd like to have more operations per entity than just:

    GET = Retreive or Query,  POST = Create,  PUT = Update, and DELETE = Delete.

So something like Upload File to Provision goes where?

public class TenantController : ApiController
     public IQuerable<TenantDto> Get() { 
// left out details

    public Post(CreateTenant createTenantCommand) {

    public Post(ProvisionTenantCommand provisionTenantCommand, Stream fileStream)

Also support seems to center on the use of ints..   As I am using Guids for Ids, I am finding it problematic as well.
Apr 25, 2012 at 3:27 PM

In my opinion, the notion of the API controller is to BE the API, not to house multiple APIs within it.  For instance, the web service space has been confusing because in ASMX, one web service could have a million methods.  In WCF, one service interface can still have a million methods.  In Web API, one ApiController cannot have a million methods (ok, you can force it to, but the model isn't designed that way).  In fact, on face value, the method names are not yours to give - they are bound to the HTTP verb.  The name you get to choose is the class name.  In my opinion, you should name the class UploadFileToProvisionController.  You can use whatever verb you like, but for compatibility, POST is probably the only verb you'll need to accept input.

In ASP.NET MVC, we find that developers add way too many actions to a single controller.  Minimizing the number of public methods per class helps here as well.  I believe our guidance in ASP.NET MVC in Action can help both with MVC controllers and API controllers.  http://manning.com/palermo3


Best regards,
Jeffrey Palermo 

Apr 25, 2012 at 4:30 PM

Interesting point...

One of the frustrations about WCF was that the overhead of having a proxy was high enough, just from a developer standpoint, that it was much easier to work with a single service... Yes, you can partition the interfaces, but since the code was in a single implementation, on a previous engagement, I ended up segmenting using partial classes.   As it was there were four different Service endpoints.    Yes, one of them had way too many methods...   The tradeoff was that I would have had to add more proxies on the client side.

In this application, an UploadFileToProvisionController does make sense.    However, in general this feels that instead of having millions of methods, that instead I will have millions of controllers, and doesn't sound like a good tradeoff.   I'd like the ability to choose my own partitioning, instead of being forced into one direction or another.

There are a lot of other things I'd like to do with the tenant though in the TenantController.   I'd like to be able to retrieve either by Guid or by ExternalId.   The database I am using (Neo4J) unfortunately doesn't support LINQ, so the IQuerable interface really doesn't help, unless I build a LINQ to Gremlin provider.

You mention that you can "force it to"..  How would I "force it" to support more than simple CRUD operations?

I'll check out your new book, though!    I have long admired your work.

Apr 25, 2012 at 4:51 PM

You can also model your api more in an RPCish way using actions.






It is all a matter of setting up the routing table.

Apr 25, 2012 at 5:33 PM
Edited Apr 25, 2012 at 5:36 PM

One problem I had was the inability to easily specify an optional Guid, and since it conflates all Getxxx methods to one, there were conflicts.

So /tenants/Get and /tenants/GetById/ are effectively the same call, as I understand it.   In order to have an optional Guid, you have to declare it nullable, but then there are other conflicts.

What I'd like to do is:


/tenants/Get -- gets all tenants - simple listing

/tenants/Get?Id=<guid>  [ as mentioned above, using IQueryable() is relatively inefficient.   There won't be *many* tenants and they can be cached so it's not too bad, but there will be many groups and users, so getting all for them is non-performant. ]

/tenants/<Guid> [ same, though not necessary ]

/tenants/Get?id=<externalId>  - externalId is a string

/tenants/<externalId> [ same, though not necessary ]

/tenants/<Guid>?detailed=true [ Detailed info on tenant and everything in a tenant ]


/tenants/<Guid> - Add Tenant with specified Guid.  - Calls TenantsController.Post(AddTenant addTenant)


/tenants/<Guid> sending UpdateTenant - Update tenant with specified Guid - Calls TenantsController.Put(UpdateTenant updateTenant)

/tenants/<Guid>/App=<AppId> sending UpdateTenantAppConfigurationData - Update configuration data for Tenant - Calls TenantsController.Put(UpdateTenantAppConfigurationData data)

/tenants/<Guid>sending GroupRole data [ group & user roles are unique to a tenant, and there are just a few, so it's a part of a "tenant' ].

/tenants/<Guid> sending UserRole data



/tenants/<Guid> - Delete tenant with specified Guid

Then for Groups we might have:

/Groups /Get?TenantId=<Guid> -- gets all groups for a tenant- simple listing

/Groups /Get?Id=<guid> [ Guid is unique across tenants ]

/Groups /<Guid> [ same, though not necessary ]

/Groups /Get?tenantId=<Guid>&id=<externalId>  - externalId is a string

/Groups /<Guid>?detailed=true [ Detailed info on tenant and everything in a tenant ]

[POST] -- some of the post options here:

/Group/<Guid>?TenantId=<Guid> - send a command that basically says: "AddGroup to Group <Guid>"

Some options to try to partition things differently...  

I could remove externalId from these interfaces, instead using a separate LookupController like this:




In all cases, they would return the Guid, either by itself or as a part of a simple high level DTO.

I also could make the AppConfiguration Data, GroupRole data, and UserRole data all have their own individual controllers.

But then there's the GroupAppConfiguration data, the UserAppConfiguration data [ there are multiple apps involved for each ].

In some cases, the config data may need it's own app specific configuration, so then I'd have more Controllers...

Apr 25, 2012 at 5:37 PM

I haven't tested all of the above scenarios, but with a route of


and model binding you should be able to implement all of it.

You can do /foo/bar as long as you have a bar method on the foo controller.

Apr 25, 2012 at 5:48 PM

Unfortunately, when I tried to do a Post(Guid id, AddTenant addTenant) and a Post(Guid id, UpdateTenant updateTenant), I had issues right there.  

I tried to find a full example on the .Net, but I found no examples of more than very basic CRUD API that use the Web API in it's latest incarnation.

I've already moving to an MVC 3 approach based on an example I found that does allow me to have full control over the mapping of URL to methods, along with some other benefits, so to provide the exact error messages would mean recreating an example.   Unfortunately, trying to resolve this issue has cost me too much time, so I have decided to move away from the beta product for now.

May 2, 2012 at 2:02 PM
Edited May 2, 2012 at 2:02 PM

I encourage people do learn a more RESTful style to understand how to model resources of an web API. Even for CRUD semantics there are much better approaches then simple mapping to one resource and POST,PUT,GET,DELETE. For instance look at this example, how to create resources in a web-centric fashion:


There are also good books and articles around on topics beyond CRUD...