42

Is there a way to perform parameter type based overloading of an Action method ? i.e. Is it possible to do the following in a Controller

public class MyController : ApiController
{
   public Foo Get(int id) { //whatever }

   public Foo Get(string id) { //whatever }

   public Foo Get(Guid id)  { //whatever }
}

If so, what changes need to be made to the Route table.

Abhijeet Patel
  • 6,562
  • 8
  • 50
  • 93
  • As a workaround you could define one method that accepts the string argument and from there delegate to helper methods based on what the argument can be parsed as. – Joanna Derks Jan 16 '13 at 09:20
  • @Joanna: That is exactly what I'm currently doing. The reason to separate it by paramter type is for generating Web API Help documentation so that each action can be documented appropriately and that callers are aware of the valid parameter types to pass in – Abhijeet Patel Jan 16 '13 at 21:07
  • @AbhijeetPatel Hmm those comments look like they break stuff ;) - I've been screwed by this in javascript – Nate-Wilkins Oct 11 '13 at 00:32
  • If I'm not wrong you can make the parameter names of all the methods unique then it should work as is i.e. make the method names as `Get(string id1)` or `Get(Guid id2)`. From client you can call it as `http://servername/My/Get?id1=23` OR `http://servername/My/Get?id2=3006ADFE-9597-4C71-8DDC-C12C10A4FCBB`. Thus you will not have to give unique action names. – RBT Dec 07 '16 at 05:24

2 Answers2

36

This kind of scenario is not well supported by the standard routing methods.

You may want to use attribute based routing instead as this gives you a lot more flexibility.

Specifically look at the route constraints where you can route by the type:

// Type constraints
[GET("Int/{x:int}")]
[GET("Guid/{x:guid}")]

Anything else will turn into a bit of a hack... e.g.

If you did attempt it using standard routing you would probably need to route to the correct action via it's name, then use reg ex's constraints (e.g. guid) to route to the required default action.

Controllers:

public class MyController : ApiController
{
   [ActionName("GetById")]
   public Foo Get(int id) { //whatever }

   [ActionName("GetByString")]
   public Foo Get(string id) { //whatever }

   [ActionName("GetByGUID")]
   public Foo Get(Guid id)  { //whatever }
}

Routes:

        //Should match /api/My/1
        config.Routes.MapHttpRoute(
            name: "DefaultDigitApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { action = "GetById" },
            constraints: new { id = @"^\d+$" } // id must be digits
        );

        //Should match /api/My/3ead6bea-4a0a-42ae-a009-853e2243cfa3
        config.Routes.MapHttpRoute(
            name: "DefaultGuidApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { action = "GetByGUID" },
            constraints: new { id = @"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$" } // id must be guid
        );

        //Should match /api/My/everything else
        config.Routes.MapHttpRoute(
            name: "DefaultStringApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { action = "GetByString" }
        );

Updated

I would normally use a POST if doing a FromBody (perhaps use the FromUri with the model instead) but your requirements could be met by adding the following.

For the controller

    [ActionName("GetAll")]
    public string Get([FromBody]MyFooSearch model)
    {
         if (model != null)
        {
            //search criteria at api/my
        }
        //default for api/my
    }

    //should match /api/my
    config.Routes.MapHttpRoute(
                name: "DefaultCollection",
                routeTemplate: "api/{controller}",
                defaults: new { action = "GetAll" }
            );
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
  • Mark: This is awesome, thanks! I also have 2 additional GET based methods. A "public Foo[] Get() {}" which is intended to get all objects of type "Foo" and a "public Foo[] Get([FromBody]MyFooSearch searchCriteria){}" which is intended for searching Foo objects based on a filter object that the caller can pass in the body. How do I set the routes for achieving this in conjuction with what you have above? – Abhijeet Patel Jan 16 '13 at 21:10
  • @Mark-Yes it works,thanks. On an unrelated note..it appears that there is no way to pass a complex object in the request body to the Get method I have defined above which accepts a search criteria. Fiddler lets me but I can't do the same via Jquery/Ajax or via any of the HttpClient APIs in .NET 4.5. Is this supported? I have read solutions wherein people have implemented this as POST but this goes against the grain of having a RESTful service. – Abhijeet Patel Jan 25 '13 at 02:34
  • @AbhijeetPatel take a look at this answer and the comment from Roy F http://stackoverflow.com/questions/978061/http-get-with-request-body basically you should either make your request a POST (and see search as a resource type, i.e. create a new search, return results) or do what is probably most natural and use Query Params i.e. all the info in the Uri. More debate here http://stackoverflow.com/questions/207477/restful-url-design-for-search. – Mark Jones Jan 25 '13 at 08:49
  • Perfect. I changed the [FromBody] to [FromUri] and that does the trick. By passing the filter criteria as query string values in the Uri, the values get deserialized into "MyFooSearch" object as expected. – Abhijeet Patel Jan 26 '13 at 19:34
  • If I'm not wrong you can make the parameter names of all the methods unique then it should work as is i.e. make the method names as Get(string id1) or Get(Guid id2). From client you can call it as http://servername/My/Get?id1=23 OR http://servername/My/Get?id2=3006ADFE-9597-4C71-8DDC-C12C10A‌​4FCBB. Thus you will not have to give unique action names. – RBT Dec 07 '16 at 05:25
8

You can code your methods as below

    [Route("api/My/{id:int}")]
    public string Get(int id)
    {
        return "value";
    }

    [Route("api/My/{name:alpha}")]
    public string Get(string name)
    {
        return name;
    }

    [Route("api/My/{id:Guid}")]
    public string Get(Guid id)
    {
        return "value";
    }