4

I'm trying to setup Web API routing for what I thought would be a very simple thing. However, it seems that routing in Web API is not consistent with different HTTP verbs. Suppose I have this controller with these actions...

public class AvalancheController : ApiControllerBase
{

    // GET api/avalanche
    public IEnumerable<Avalanche> Get() {}

    // GET api/avalanche/5
    public Avalanche Get(int id) {}

    // GET api/avalanche/ActionTest/5
    [ActionName("ActionTest")]
    public Avalanche GetActionTest(int id) {}

    // GET api/avalanche/ActionTest/2
    [ActionName("ActionTest2")]
    public Avalanche GetActionTest2(int id) {}

    // POST api/avalanche
    public void Post([FromBody]Avalanche value) {}

    // PUT api/avalanche/5
    public void Put(int id, [FromBody]Avalanche value) {}

    // PUT api/avalanche/test/5
    [ActionName("Test")]
    public void PutTest(int id, [FromBody]Avalanche value) {}

    // DELETE api/avalanche/5
    public void Delete(int id) {}
}

and I have the following routes defined...

    config.Routes.MapHttpRoute(
        name: "ActionRoutes",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional },
        constraints: new
            {
                controller = "Avalanche",
                action = "(ActionTest|ActionTest2|Test)"
            }
    );


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

Then I end up with the following routes being defined...

GET api/Avalanche/ActionTest/{id}
GET api/Avalanche/ActionTest2/{id}
PUT api/Avalanche/Test/{id}
GET api/Avalanche   
POST api/Avalanche  
DELETE api/Avalanche/{id}

Why doesn't the default PUT route get picked up? What's different between the routing of the default GET and the default PUT? I've tried decorating the functions in every imaginable way but I get the same results.

Mainly I want to know how to get the default PUT route to be picked up. If you have any suggestions on how to modify these routes so that I don't have to have a route for each controller to specify action names that would be fantastic also.

Thanks!

Ian

EDIT: I noticed this morning that the following route is also not being defined..

GET api/Avalanche/{id}
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Ian Lee
  • 370
  • 1
  • 3
  • 15
  • http://stackoverflow.com/questions/9854602/asp-net-web-api-405-http-verb-used-to-access-this-page-is-not-allowed-how/14465655 – Ali Mar 18 '16 at 11:32

1 Answers1

1

Glad you've found solution for your problem. But I would provide my feedback based on my learning with REST services. Idea for REST webservice, is to resolve each url to a resource (or maybe entity) and depending upon HttpVerb, operation is decided. In this case, you've three GET operations, which works fine with your modification.

But I think controllers can also be re-arranged to have single GET operation and have single responsibility thus better maintainability. For ex:

AvalancheController

public class AvalancheController : ApiControllerBase
{
    public IEnumerable<Avalanche> GET()
    {

    }

    public void POST(Avalanche avalanche)
    {

    }
}

It can be assumed to deal with all avalanche (s) on top level, below are the operations to be defined.

GET : returns all avalanche
POST: inserts new avalanche
PUT: not used
DELETE: not used

AvalancheDetailsController

public class AvalancheDetailsController : ApiControllerBase
{
    public Avalanche GET(int id)
    {

    } 


    public int PUT(int id)
    {

    }

    public int DELETE(int id)
    {

    }
}

It can be assumed to deal with single avalanche, below are the operations to be defined.

GET : returns single avalanche
POST: not used
PUT: updates single avalanche
DELETE: deletes single avalanche

Now I assume we have clear distinction of between controllers. In the OP you've mentioned, there can be different GET operations, but it returns only single Avalanche. So, I would change GET method to take object as input and check for values i.e,

public class AvalanceRequest
{
  public int? Id {get;set;}
  public string Name {get;set;}
}


   public class AvalancheDetailsController : ApiControllerBase
    {
        public Avalanche GET(AvalanceRequest request)
        {
           //write business logic based on parameters
           if(request.Id.HasValue)
           //return avalanche;
           if(request.Name.IsNullOrEmpty())
           //return avalanche
        } 
        //other methods
  }

Dealing with URL, I didn't really work with WebAPI but was trying ServiceStack to develop REST services. It allows to attach url's independent of controller names.

Url
api/Avalanche --> AvalancheController (Operations are called based on HttpVerb)
api/Avalanche/Id --> AvalancheDetailsController (Operations are called based on HttpVerb)

I don't know whether url's can be attached likewise in WebAPI, otherwise you end up having default config and call via. api/Avalanche and api/AvalancheDetails/id.

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

I am sorry for long post, hope it makes sense.

Sunny
  • 4,765
  • 5
  • 37
  • 72
  • Interesting approach. I'm going to have to think about it more. This is my first REST API, so I'm certainly open to all ideas. The idea that I've been following is that the container would describe the main data type being acted upon and using actions to describe more difficult operations. For example, what if you need more sophisticated lookups that return an array of Avalanche items such as GetAvalancheCreatedWithinDateRange()? Very quickly the idea of having one function per verb breaks down. – Ian Lee Mar 14 '13 at 20:17
  • @IanLee, I am also still learning. Right, its better to think before implementation. For your question, since its a filter on the IEnumerable, I would have custom object with date1 and date2 as parameters on GET method. But as you said, there may be such complex situtations, I came across this article, which explains such implementations http://www.codeproject.com/Articles/21258/Everything-about-REST-web-services-what-and-how-Pa – Sunny Mar 14 '13 at 21:33