8

UPDATE

My original assumption was that optional parameters were the cause of the problem. That appears to be incorrect. Instead, it appears to be a problem with multiple action methods when one of those methods contains nullable value types (e.g. int? ) for some of the parameters.

I'm using Visual Studio 2012 RC and am just getting started with Web API. I've run into an issue and getting the error "No action was found on the controller 'Bars' that matches the request."

I've got a Bars controller. It has a Get() method that takes in optional parameters.

public IEnumerable<string> Get(string h, string w = "defaultWorld", int? z=null)
{
    if (z != 0)
        return new string[] { h, w, "this is z: " + z.ToString() };
    else
       return new string[] { h, w };
}

So, I test it out with the following urls

  • /api/bars?h=hello
  • /api/bars?h=hello&w=world
  • /api/bars?h=hello&w=world&z=15

And it works for all three.

Then, I go to add another Get() method, this time with a single id parameter

 public string Get(int id)
 {
     return "value";
 }

I test the urls again. This time /api/bars?h=hello&w=world and api/bars?h=hello fail. The error message is "No action was found on the controller 'Bar' that matches the request."

For some reason, these two methods don't play nicely together. If I remove Get(int id), it works. If I change int? z to string z, then it works (, but then it requires converting the objects inside my action method!).

Why is Web API doing this? Is this a bug or by design?

Many thanks.

John
  • 3,332
  • 5
  • 33
  • 55

3 Answers3

3

I haven't found a true answer for this issue yet (why is Web API doing this), but I have a workaround that does allow for an overloaded Get(). The trick is to wrap the parameter values in an object.

public class Foo
{
    public string H { get; set; }
    public string W { get; set; }
    public int? Z { get; set; }
}

And to the Bars controller modify to

public IEnumerable<string> Get([FromUri] Foo foo)
{
    if (foo.Z.HasValue)
        return new string[] { foo.H, foo.W, "this is z: " + foo.Z.ToString() };
    else
        return new string[] { foo.H, foo.W, "z does not have a value" };
}

[FromUri] is necessary, because WebAPI does not, by default, use URI parameters to form "complex" objects. The general thinking is that complex objects are coming from <form> actions, not GET requests.

I'm still going keep checking about why Web API behaves this way and if this is actually a bug or intended behavior.

Hendrik W. Hansen
  • 391
  • 1
  • 3
  • 14
John
  • 3,332
  • 5
  • 33
  • 55
2

You can overload WEB API Controller methods by adding an action parameter in the route.

 routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new {action = "Get", id = RouteParameter.Optional }
        );

Once you make this change in your route then you can call your methods like

/api/bars/get?id=1
/api/bars/get?h=hello&w=world&z=15

Hope this help.

Omar

Paige Cook
  • 22,415
  • 3
  • 57
  • 68
Bah
  • 101
  • 1
2

Problem solved, although, it leaves an additional question. The problem appears to be that the overloaded Action methods are having problems with the optional parameters.

So the new question is why so, but I will leave that up to lower level guys than me ;)

But this is good news. I didn't like the problem you reported, and going the complex type route, while nice to know, is simply a jerry rig fix and would reflect very poorly on how something is working in the Web Api. So the good news is, if you have this problem, it is solved by simply doing away with the optional params, do the good ol' overloads route. Good news, as this is by no means a jerry rig fix, simply makes you loose a little optional parameter convenience:

public class BarsController : ApiController
{
    public string Get(int id)
    {
        return "value";
    }

    public IEnumerable<string> Get(string h)
    {
        return Get(h, null, null);
    }

    public IEnumerable<string> Get(string h, string w)
    {
        return Get(h, w, null);
    }

    public IEnumerable<string> Get(string h, string w, int? z) 
    {
        if (z != 0)
            return new string[] { h, w, "this is z: " + z.ToString() };
        else
            return new string[] { h, w };
    }
}

Cheers

Cornelius
  • 830
  • 11
  • 31
Nicholas Petersen
  • 9,104
  • 7
  • 59
  • 69
  • This is a good solution for a small number of variables. Overloads become a real pain when more than one parameter is optional. The particular work problem that I was facing use a larger set of variables used to filter the data being returned. In those cases, it makes more sense to create a custom Filter() or Options() class to handle all the incoming parameters. Incidentally, you could edit your 3rd method to use int z, instead of int? z. – John Aug 02 '12 at 15:26
  • @John "What happens if z is null?" Good question! I thought that too, but was just copying the original code at that point as that was not the problem. So there should be a check for null: if(z != null && z != 0). UPDATE: ?? Umm, John, just realized you were the original author ... that was your own code mate! – Nicholas Petersen Aug 10 '12 at 07:02
  • @John To the main content though, hey good point, it really could be advantageous to simply wrap this up in a custom type as you did. However, the main point of my answer was simply to point where the problem was, and that is that WebAPI (or ASP.NET) is not properly handling overloaded methods as it should. Is it a bug? I don't know, but it would be nice if it could be fixed. – Nicholas Petersen Aug 10 '12 at 07:08
  • As bit of an aside, for POST and PUT, if you're doing a form submit and the variables are included in the request body (default), you MUST use a custom object in Web API, because Web API can only handle one variable coming from the request body. So it seems, to me at least, that in Web API, using custom objects is more the norm than the exception. – John Aug 10 '12 at 13:18