8

I want to have two different GET action to query the data by, name and id,

I have these routes:

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

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

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

and these actions in the controller:

    [HttpGet]
    public CompanyModel CompanyId(Guid id)
    {
          //Do something
    }


    [HttpGet]
    public CompanyModel CompanyName(string name)
    {
            //Do something
    }

while a call like this: http://localhost:51119/api/companies/CompanyId/3cd97fbc-524e-47cd-836c-d709e94c5e1e works and gets to the 'CompanyId' method,

a similar call to http://localhost:51119/api/companies/CompanyName/something gets me to 404 not found

but this: 'http://localhost:51119/api/companies/CompanyName/?name=something' works fine

Can anyone explain this behaviour and what am I doing wrong?

j0k
  • 22,600
  • 28
  • 79
  • 90
Doron Sinai
  • 1,166
  • 3
  • 12
  • 28

1 Answers1

10

The Web API route selector has no way to know if the string at the end of your URL is a GUID or not. Therefore, it is not about to select the correct route for the appropriate GET action.

In order to select the correct route, you need to add a route constraint for the GUID uri template.

    public class GuidConstraint : IHttpRouteConstraint
    {
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values,
                          HttpRouteDirection routeDirection)
        {
            if (values.ContainsKey(parameterName))
            {
                string stringValue = values[parameterName] as string;

                if (!string.IsNullOrEmpty(stringValue))
                {
                    Guid guidValue;

                    return Guid.TryParse(stringValue, out guidValue) && (guidValue != Guid.Empty);
                }
            }

            return false;
        }
    }

And then, add the constraint to the route that will handle the GUID.

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

Since this route is more specific than the general "string" route, it needs to be placed above the one that is going to resolve name.

This should appropriately route to the actions.

Hope this helps.

Davin Tryon
  • 66,517
  • 15
  • 143
  • 132
  • I accept your answer because this works great, but I still don't understand why it's not mapped to the right action, in the URI I direct is once to 'CompanyName' and once to 'CompanyId', should't it select the appropriate action? – Doron Sinai Mar 28 '13 at 12:25
  • 2
    [This SO question](http://stackoverflow.com/questions/9569270/custom-method-names-in-asp-net-web-api) might help explain why. Web API routes follow REST conventions by default. So, you might see different behavior that with ordinary MVC routes. – Davin Tryon Mar 28 '13 at 12:44
  • Brilliant! Web API is just so well thought out! – Karthic Raghupathi Aug 23 '13 at 03:33