27

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 }
            );
Tom Rider
  • 2,747
  • 7
  • 42
  • 65

8 Answers8

22

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.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Admir Tuzović
  • 10,997
  • 7
  • 35
  • 71
8

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 }
);
KreepN
  • 8,528
  • 1
  • 40
  • 58
James
  • 80,725
  • 18
  • 167
  • 237
6

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();
}
Bart
  • 5,065
  • 1
  • 35
  • 43
3

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 :)

Ali Adravi
  • 21,707
  • 9
  • 87
  • 85
2

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)
Jonathan Amend
  • 12,715
  • 3
  • 22
  • 29
0

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...

Ramesh
  • 1,692
  • 2
  • 18
  • 37
0

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.

Deepak Raj
  • 730
  • 2
  • 9
  • 26
0

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 }
        );
Parsa Karami
  • 702
  • 1
  • 8
  • 30