Web API routing with multiple parameters

Mourndark picture Mourndark · Jan 15, 2014 · Viewed 92.7k times · Source

I'm trying to work out how to do the routing for the following Web API controller:

public class MyController : ApiController
{
    // POST api/MyController/GetAllRows/userName/tableName
    [HttpPost]
    public List<MyRows> GetAllRows(string userName, string tableName)
    {
        ...
    }

    // POST api/MyController/GetRowsOfType/userName/tableName/rowType
    [HttpPost]
    public List<MyRows> GetRowsOfType(string userName, string tableName, string rowType)
    {
        ...
    }
}

At the moment, I'm using this routing for the URLs:

routes.MapHttpRoute("AllRows", "api/{controller}/{action}/{userName}/{tableName}",
                    new
                    {
                        userName= UrlParameter.Optional,
                        tableName = UrlParameter.Optional
                    });

routes.MapHttpRoute("RowsByType", "api/{controller}/{action}/{userName}/{tableName}/{rowType}",
                    new
                    {
                        userName= UrlParameter.Optional,
                        tableName = UrlParameter.Optional,
                        rowType= UrlParameter.Optional
                    });

but only the first method (with 2 parameters) is working at the moment. Am I on the right lines, or have I got the URL format or routing completely wrong? Routing seems like black magic to me...

Answer

Tom Stickel picture Tom Stickel · Sep 22, 2015

I have seen the WebApiConfig get "out of control" with hundreds of routes placed in it.

Instead I personally prefer Attribute Routing

You are making it confusing with POST and GET

[HttpPost]
public List<MyRows> GetAllRows(string userName, string tableName)
{
   ...
}

HttpPost AND GetAllRows ?

Why not instead do this:

[Route("GetAllRows/{user}/{table}")]
public List<MyRows> GetAllRows(string userName, string tableName)
{
   ...
}

OR change to Route("PostAllRows" and PostRows I think that you are truly doing a GET request so the code I show should work for you. Your call from client would be WHATEVER is in the ROUTE , so it WILL FIND your METHOD with GetAllRows, but the method itself , that name CAN BE anything that you want, so as long as the caller matches the URL in ROUTE, you could put in GetMyStuff for the method if you really wanted to.

Update:

I actually prefer to be explicit with type of HTTP methods AND I prefer to match the route params to the method params

[HttpPost]
[Route("api/lead/{vendorNumber}/{recordLocator}")]
public IHttpActionResult GetLead(string vendorNumber, string recordLocator)
{ .... }

(the route lead does not need to match method name GetLead but you will want to keep the same names on the route params and the method params, even though you can change the order e.g. put recordLocator before vendorNumber even if the route is the opposite - I don't do that , as why make it more confusing to look at).

Bonus: Now you can always use regex in routes as well, example

[Route("api/utilities/{vendorId:int}/{utilityType:regex(^(?i)(Gas)|(Electric)$)}/{accountType:regex(^(?i)(Residential)|(Business)$)}")]
public IHttpActionResult GetUtilityList(int vendorId, string utilityType, string accountType)
    {