27
votes

I am using web API and i am new in this. I am stuck in a routing problem. I have a controller with following actions :

    // GET api/Ceremony
    public IEnumerable<Ceremony> GetCeremonies()
    {
        return db.Ceremonies.AsEnumerable();
    }

    // GET api/Ceremony/5
    public Ceremony GetCeremony(int id)
    {
        Ceremony ceremony = db.Ceremonies.Find(id);
        return ceremony;
    }

    public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
    {
        return filter.Ceremonies();
    }

The problem occure when i added the action GetFilteredCeremonies to my controller. After adding this when i make an ajax call to GetCeremonies action then it return an Exception with following message :

"Message":"An error has occurred.","ExceptionMessage":"Multiple actions were 
 found that match the request

FYI: The parameter Search is the Model class which contains properties and a function name Ceremonies.

EDIT

Route:

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
8
Can you share your routs as well?indiPy
@alliswell please check my editTom Rider
I think this is the same issue as stackoverflow.com/questions/9499794/…Davin Tryon
Have two methods in controller with name GetCeremony() try to resolve them as they both pointed to same routeSaurabh Chauhan

8 Answers

22
votes

If you're not requirement bound to use REST services that use api/{controller}/{id} route and attempt to resolve action based on method GET/POST/DELETE/PUT, you can modify your route to classic MVC route to api/{controller}/{action}/{id} and it will solve your problems.

8
votes

The problem here is your 2 Get methods will resolve to api/Ceremony and MVC does not allow parameter overloading. A quick workaround (not necessarily the preferred approach) for this sort of problem is to make your id parameter nullable e.g.

// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies(int? id)
{
    if (id.HasValue)
    {
        Ceremony ceremony = db.Ceremonies.Find(id);
        return ceremony;
    }
    else
    {
        return db.Ceremonies.AsEnumerable();
    }
}

However, you would then be returning a list of ceremonies when with 1 item when your trying to query for a single ceremony - if you could live with that then it may be the solution for you.

The recommended solution is to map your paths appropriately to the correct actions e.g.

context.Routes.MapHttpRoute(
    name: "GetAllCeremonies",
    routeTemplate: "api/{controller}",
    defaults: new { action = "GetCeremonies" }
);

context.Routes.MapHttpRoute(
    name: "GetSingleCeremony",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { action = "GetCeremony", id = UrlParameter.Optional }
);
5
votes

Luckily nowadays with WEB API2 you can use Attribute Routing. Microsoft has gone open source on a big scale and then a wizard named Tim McCall contributed it from the community. So since somewhere end 2013, early 2014 you can add attributes like [Route("myroute")] on your WEB API methods. See below code example.

Still - as I just found out - you have to make sure to use System.Web.Http.Route and NOT System.Web.Mvc.Route. Otherwise you'll still get the error message Multiple actions were found that match the request.

using System.Web.Http;
...

[Route("getceremonies")]
[HttpGet]
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies()
{
    return db.Ceremonies.AsEnumerable();
}

[Route("getceremony")]
[HttpGet]
// GET api/Ceremony/5
public Ceremony GetCeremony(int id)
{
    Ceremony ceremony = db.Ceremonies.Find(id);
    return ceremony;
}

[Route("getfilteredceremonies")]
[HttpGet]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
{
    return filter.Ceremonies();
}
3
votes

Here is my controller:

public class PhoneFeaturesController : ApiController
{
    public List<PhoneFeature> GetbyPhoneId(int id)
    {
        var repository = new PhoneFeatureRepository();
        return repository.GetFeaturesByPhoneId(id);
    }

    public PhoneFeature GetByFeatureId(int id)
    {
        var repository = new PhoneFeatureRepository();
        return repository.GetFeaturesById(id);
    }        
}

Here is my api routing:

        config.Routes.MapHttpRoute(
            name: "ApiWithId", 
            routeTemplate: "Api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: new { id = @"^[0-9]+$" });

        config.Routes.MapHttpRoute(
            name: "ApiWithAction", 
            routeTemplate: "api/{controller}/{action}/{name}",
            defaults: null,
            constraints: new { name = @"^[a-z]+$" });

        config.Routes.MapHttpRoute(
            name: "ApiByAction",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { action = "Get" },
            constraints: new { id = @"^[0-9]+$" });

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

I tested it like this:

/api/PhoneFeatures//api/PhoneFeatures/GetFeatureById/101

/api/PhoneFeatures/GetByFeatureId/12

It works smooth in every condition :)

1
votes

I found another fix that doesn't require moving methods out of the controller or changing the route mapping config. Just add the [NonAction] attribute to the method you want to exclude:

[NonAction]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
0
votes

Please check you have two methods which has the different name and same parameters.

If so please delete any of the method and try.

This error was raised because there are two methods which are looking for same parameters. try to delete any one of them and try...

0
votes

I hope you are doing HttpGet while you invoke GetFilteredCeremonies(Search filter)

In that case, you cannot pass complex object in GET request like Search that you are passing.

If for some reason, you definitely want to get complex types in your get request, there are some work around. You may need to write a custom model binder and then set the attribute. please refer this article.

0
votes

Edit Your WebApiConfig.cs in App_Start folder on the root of project and add {action} to routeTemplate parameter in MapHttpRoute Method like below :

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