Web API 2 routing attributes not working

Jeff Guillaume picture Jeff Guillaume · Oct 31, 2013 · Viewed 9.8k times · Source

I'm using the final release versions of .NET 4.5 and Web API 2 (in Visual Studio 2013). I've been using this documentation as a reference, to no avail.

I have a few base routes like

api/providers
api/locations
api/specialties

And some methods on each like

Get()
Get(int id)
Get(string keyword)
Autocomplete(string keyword)
Search(string zipcode, string name, int radius, [...])

Ideally, I'd like the URLs to end up like

  • /api/locations/12345 (get location 12345)
  • /api/locations/clinical (get locations with "clinical" in the name)
  • /api/locations/autocomplete?keyword=clinical (get a condensed Id + Name object for locations with "clinical" in the name)
  • /api/locations/search?zipcode=12345&radius=20&name=clinical (get locations within 20 miles of zip 12345 with "clinical" in the name)

In the chunk of code below, the Get methods and Search work as desired, but Autocomplete does not. It should be noted I have similarly named methods in multiple controllers. What am I doing wrong? (Also, what exactly is the Name = property for?)

/// <summary>
/// This is the API used to interact with location information.
/// </summary>
[RoutePrefix("api/locations")]
public class LocationController : ApiController
{
    private ProviderEntities db = new ProviderEntities();

    private static readonly Expression<Func<Location, LocationAutocompleteDto>> AsLocationAutocompleteDto =
        x => new LocationAutocompleteDto
        {
            Id = x.Id,
            Name = x.Name
        };

    /// <summary>
    /// Get ALL locations.
    /// </summary>
    [Route("")]
    public IQueryable<Location> Get()
    {
        return db.Locations.AsQueryable();
    }

    /// <summary>
    /// Get a specific location.
    /// </summary>
    /// <param name="id">The ID of a particular location.</param>
    [Route("{id:int}")]
    public IQueryable<Location> Get(int id)
    {
        return db.Locations.Where(l => l.Id == id);
    }

    /// <summary>
    /// Get all locations that contain a keyword.
    /// </summary>
    /// <param name="keyword">The keyword to search for in a location name.</param>
    [Route("{keyword:alpha}")]
    public IQueryable<Location> Get(string keyword)
    {
        return db.Locations.Where(l => l.Name.Contains(keyword)).OrderBy(l => l.Name);
    }

    [Route("search", Name = "locationsearch")]
    public string Search(string zipcode = null, string latitude = null, string longitude = null)
    {
        if (zipcode != null) return "zipcode";
        if (latitude != null && longitude != null) return "lat/long";
        else
            return "invalid search";
    }

    /// <summary>
    /// Autocomplete service for locations, returns simple Id/Name pairs.
    /// </summary>
    /// <param name="keyword">The keyword to search on.</param>
    [Route("autocomplete/{keyword:alpha}", Name = "locationautocomplete")]
    public IQueryable<LocationAutocompleteDto> Autocomplete(string keyword)
    {
        // validate the inputs
        if (string.IsNullOrEmpty(keyword))
            return null;

        IQueryable<Location> results = from l in db.Locations
                                       where l.Name.Contains(keyword)
                                       select l;

        return results.Select(AsLocationAutocompleteDto);
    }

There does not seem to be a lot of community support around the latest versions of this stuff yet.

EDIT (with answer)

Simply replaced

[Route("autocomplete/{keyword:alpha}", Name = "locationautocomplete")]

with

[Route("autocomplete"), Route("autocomplete/{keyword:alpha}", HttpGet()]

and it works perfectly.

Answer

Kiran Challa picture Kiran Challa · Oct 31, 2013

Are you trying to do a GET request on Search and AutoComplete actions? If yes, then you would need to decorate with a System.Web.Http.HttpGet attribute. If an action isn't prefixed by verbs like Get, Put, Post, Delete etc, Web API by defaults assumes as POST. I am guessing you are receiving a 405 Method Not Allowed..right?

The Name property is useful in case of link generation (Url.Link) where you would need to specify a name