Search code examples
c#asp.netasp.net-web-api-routing

Different Web API 2 controllers when using attribute routing


I'm building a prototype for a RESTful API using ASP.NET Web API 2. Fo simplictiy let's assume I have three entities: customers, licences and users. Each customer has a set of licences and users. Semantically it seems to me the resource URIs should look like this:

myurl/api/customers for accessing all customers
myurl/api/customers/{cid} for accessing the customer {cid}
myurl/api/customers/{cid}/licences for accessing all licences of customer {cid}
myurl/api/customers/{cid}/licences/{lid} for accessing the licence {lid} of customer {cid}

The same goes for the users. The intended semantics allow for example two users to have the same id if they belong to separate customers. Apart from maybe the licences entities (decision not final yet) each customer will have a dedicated database, so there is no overlapping in this domain and resource paths like

myurl/api/users

make only sense in the way "join all user tables from all customers' databases.

Using attribute routing this setup is quite easily achieved. However, all methods have to be implemented in the same controller since methods from different controllers cannot share the same prefix AFAIK.

The actual application will contain many more entities than just three, so I expect the controller's implementation to get quite huge. My question now is, how can I split the method into different controllers? I thought about using one main controller which just dispatches the work to be done to another controller. For example

[Route("{customer:int}/licences/{licence:int}")]
public HttpResponseMessage GetLicence(int customer, int licence)
{
    // pretend the called method is static
    return LicenceController.GetLicence(customer, licence);
}

However, I do not know how to implement this properly: Should I create a new LicenceController for each call? Or have a property of this type and call it's method? Actually implement some static methods?

Another downside is that this introduces hard-coded dependencies between the selector and the implementing controller classes which I feel is not a clean solution.

I came up with a workaround which uses resource paths like this:

myurl/api/licences/customer-{cid} for accessing all licences of customer {cid}
myurl/api/licences/customer-{cid}/{lid} for accessing the licence {lid} of customer {cid}

This works quite well but messes up the homogeneous semantics IMO. I know I can write a custom selector class but that seems to be quite some work to get it right.

So my question is, what is the best (perhaps most efficient) way to split the code which deals with incoming HTTP messages into separate controllers so that there is loose coupling and the resource semantics are coherent?


Solution

  • You would have two controllers. One to return the customers and one to return the licences. For the Customer there is no need to use attributes as the defaults are fine:

    public class CustomersController : ApiController
    {
        // GET: api/Customers
        public IEnumerable<Customer> Get()
        {
            return new List<Customer> 
            {
                new Customer { Id = 1, Name = "Wayne" },
                new Customer { Id = 2, Name = "John" }
            };
        }
    
        // GET: api/Customers/5
        public Customer Get(int id)
        {
            return new Customer { Id = 1, Name = "Wayne" };
        }
    }
    

    Then you can you RoutePrefix attribute on the controller to add for api/Customers/1/licences and the rest can be handled by Route on the actions. I named the Controller CustomerLicencesController as you probably want to have a Licences controller to fetch a particular licence or all licences such as api/licences or api/licences/1.

    [RoutePrefix("api/customers/{customer}/licences")]
    public class CustomerLicencesController : ApiController
    {
        // GET: api/Customers/1/licences
        [Route("")]
        public IEnumerable<Licence> Get(int customer)
        {
            return new List<Licence>
            {
                new Licence { Id = 1, Name = "Test" },
                new Licence { Id = 2, Name = "Test2" }
            };
        }
    
        // GET: api/Customers/1/licences/1
        [Route("{id}")]
        public Licence Get(int customer, int id)
        {
            return new Licence { Id = 1, Name = "Test" };
        }
    }
    

    For more information about Route attributes take a look at this.