Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebApi Multiple actions were found with GetAll() and GetByIds(int[] ids)

Using the standard route:

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

With these actions:

public class ValuesController : ApiController
{
    // GET api/values
    public string GetAll()
    {
        return "all";
    }

    // GET api/values/5
    public string GetById(int id)
    {
        return "single";
    }

    // GET api/values?ids=1&ids=2
    public string GetByIds([FromUri] int[] ids)
    {
        return "multiple";
    }

And make a request to /api/values, I get this exception:

Multiple actions were found that match the request: 
System.String GetAll() on type MvcApplication4.Controllers.ValuesController
System.String GetByIds(Int32[]) on type MvcApplication4.Controllers.ValuesController

I've been spinning my wheels trying to find a solution around this. It's my belief that the GetAll and GetByIds actions are considered Multiple here, but they aren't because the GetByIds has a different signature.

Is there a work around for this that doesn't involve adding {action} to the route?

like image 615
Levitikon Avatar asked Apr 15 '13 17:04

Levitikon


People also ask

Can we have multiple Get methods in Web API?

As mentioned, Web API controller can include multiple Get methods with different parameters and types. Let's add following action methods in StudentController to demonstrate how Web API handles multiple HTTP GET requests.

What is attribute routing in ASP net Web API 2. 0?

Routing is how Web API matches a URI to an action. Web API 2 supports a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API.


3 Answers

Thanks for the input everyone. After kicking options around, the only way I found to do this, is to combine the GetAll and GetByIds action and switch case the length of ids.

public class ValuesController : ApiController
{
    // GET api/values/5
    public string GetById(int id)
    {
        return "single";
    }

    // GET api/values
    // GET api/values?ids=1&ids=2
    public string GetByIds([FromUri] int[] ids)
    {
        switch (ids.Length)
        {
            case 0:
                return "all";

            default:
                return "multiple";
        }
    }
like image 50
Levitikon Avatar answered Oct 21 '22 19:10

Levitikon


We currently do not have out of box support for binding collection of values coming from Uri. Following is the issue regarding that and also the action disambiguation problem:

http://aspnetwebstack.codeplex.com/workitem/322

Unfortunately, i cannot think of a work around related to the Action selection problem(without the '{action}') itself even though you solve the problem of modelbinding to collection using a custom parameter binding like below:

public string GetByIds(int[] ids)
    {
        return "multiple";
    }
------------------------

config.ParameterBindingRules.Insert(0, typeof(int[]), (paramDesc) => new SampleParameterBinding(paramDesc));

-------------------------

public class SampleParameterBinding : HttpParameterBinding
{
    public SampleParameterBinding(HttpParameterDescriptor desc)
        : base(desc)
    {
    }

    public override bool WillReadBody
    {
        get
        {
            return false;
        }
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        HttpRequestMessage currentRequest = actionContext.Request;

        NameValueCollection nvc = currentRequest.RequestUri.ParseQueryString();

        //TODO: ERROR CHECKS
        int[] ids = nvc["ids"].Split(',').Select(str => Int32.Parse(str)).ToArray();

        // Set the binding result here
        SetValue(actionContext, ids);

        // now, we can return a completed task with no result
        TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>();
        tcs.SetResult(default(AsyncVoid));
        return tcs.Task;
    }

    private struct AsyncVoid
    {
    }
}
like image 25
Kiran Avatar answered Oct 21 '22 18:10

Kiran


I'd recommend attribute routing:

[RoutePrefix("api")]
public class ValuesController : ApiController
{
    // GET api/values
    // GET api/values?ids=1&ids=2
    [Route("values")]
    public string GetCollection([FromUri] IList<int> ids)
    {
        if (ids == null)
        {
          return "all";
        }
        return "multiple";
    }

    // GET api/values/5
    [Route("values/{id:int}")]
    public string GetById(int id)
    {
        return "single";
    }
like image 39
Tom Dunn Avatar answered Oct 21 '22 18:10

Tom Dunn