Jack
Jack

Reputation: 2660

Web API route not mapped to the correct url

I am trying to access the following url :

http://localhost:2727/api/SiteApi/Get?campaignId=2

Here is configuration:

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

Action trying to mapped to

public class SiteApiController : ApiController
{
    ...
    public IEnumerable<Site> GetByCampaignId(int campaignId)
    {
        ...
        return sites;   
    }      
}

This is currently return "No action found". However, the Url will work if swap the order of the routes. This means there was a match.

Question: My understanding of routing is that if it doesn't match the first route, it will fall back to the second route. I can understand if it's mapped to the wrong route, but it shouldn't be an error indicating something like "No match found" regardless how I order them.

Have I missed anything?

Upvotes: 1

Views: 4561

Answers (4)

Jack
Jack

Reputation: 2660

Thanks for all the answers been given above. Some of them are quite interesting, however, those aren't really what I am looking for.

Basically, my question is not how I should map the URI, rather why it is not mapping to the correct route. Fortunately, I've figure out the answer after doing some testing.

So why "No action found"? Simply because it matched the first route, which I didn't expect. Because the Id is set to optional, so it's matching any routes with two parameter. This is why "SiteAPI" mapped to controller and "Get" mapped to Action. Since campaignId is in query string, it gets ignored when it try to map url in this case.

There are other bits and pieces in the routing which i didn't mention, but this should be enough. Refer to Mark Jones's answer if you want more details.

Upvotes: 0

Mark Jones
Mark Jones

Reputation: 12194

Updated

Good resource: http://msdn.microsoft.com/en-us/library/cc668201(v=vs.100).aspx

Route matching is tried from the first route to the last route in the collection. When a match occurs, no more routes are evaluated. In general, add routes to the Routes property in order from the most specific route definitions to least specific ones.

Your reason for getting "No action found" is that your first route matches. It simply cannot find the action Get - because you have named your method GetByCampaignId

http://localhost:2727/api/SiteApi/Get?campaignId=2

So your understanding is correct - however your URL does match the first route (it just cannot find the action).

This will map the action to your method

[ActionName("Get")]
public IEnumerable<Site> GetByCampaignId(int campaignId)
{

}

Or simply change your url to

http://localhost:2727/api/SiteApi/GetByCampaignId?campaignId=2

How to model your route

Remove the "Get" from the url.

You appear to want campaignId specified on the query string.

You have declared GetByCampaignId(int campaignId) without the [FromUri] attribute therefore the campaignId is assumed to be in the path i.e. as it stands this method is matching /api/siteapi/1.

If campaignId is optional Change your method signature like so:

public IEnumerable<Site> GetByCampaignId([FromUri]int? campaignId = null)
{
    if(campaignId.HasValue)
    {
       //....
    }
    return sites;   
}   

If campaignId is manditory to your route change your method signature like so:

public IEnumerable<Site> GetByCampaignId([FromUri]int campaignId)
{
    return sites;   
}   

Upvotes: 4

Andrei Drynov
Andrei Drynov

Reputation: 8592

I think you are over-complicating things. Just use the default route.

Your URL does not need the Get part:

http://localhost:2727/api/SiteApi/Get?campaignId=2

It can be

http://localhost:2727/api/SiteApi/?campaignId=2

because your method has Get in its name which means it will respond to the HTTP GET request (conventions are good: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api). It will see the action parameter "campaignId" and say "Aha, I'll go to that action"

public IEnumerable<Site> GetByCampaignId(int campaignId)

When you swapped the routes, then the default route kicked in - and won.

The additioanal "DetailedApi" route is not needed, imho.

Upvotes: 2

Bernardo
Bernardo

Reputation: 1030

I would recommend using this:

http://nuget.org/packages/routedebugger

to help debug your routes.

I know you didn't ask, but your sample URI is not RESTful.

To match the second route, you should try to hit this URI:

http:// localhost:2727/api/SiteApi/Get/2

Your first mapping should not catch this route since there is no "2" Action and it would fall back on the second mapping. But, I could see this causing trouble: I wouldn't use your mapping. IMHO, you're trying to achieve the same as the following, which works:

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

Upvotes: 1

Related Questions