Question

I have an MVC Web Api RESTful service. I am not using API version 2.

I created a Controller with the default GET/POST/PUT/DELETE. Everything was fine until I decided to add some more functionality to my controller.

Long story short, I ended up with near a dozen route templates (very nasty WebApiConfig file) for my controller. It was working fine until.... i needed a second controller. Then the chaos began.

All the routes for the DocumentController obviously didn't work for CompanyController because what I had previously configured was just a bunch of crappy patches to make my first Controller work. Here I am four days after and still can't get it to work. I truly can't grasp my head around this whole routing thing. So complicated and inflexible (in my opinion).

Here is some code for you to understand.

public class DocumentController : ApiController
{
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse(HttpStatusCode.OK,"This is GET");
    }

    [HttpGet]
    public HttpResponseMessage Metadata()
    {
        return Request.CreateResponse(HttpStatusCode.OK,"This is Metadata");
    }

    public HttpResponseMessage Get(int id)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is GET with ID");
    }

    public HttpResponseMessage Post([FromBody] DocumentDTO document)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is Post");
    }

    public HttpResponseMessage Put(int id, [FromBody] DocumentDTO document)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is PUT");
    }

    [HttpGet]
    public HttpResponseMessage Tags(int id)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is GET TAGS");
    }

    [HttpPost]
    public HttpResponseMessage Tags(int id, [FromBody] IEnumerable<TagDefinitionDTO> tagDefinitions)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is POST TAGS");
    }
}

So, my initial dream was to achieve the following

GET /document/ => ALL DOCUMENTS

GET /document/3 => Document Id=3

POST /document/ => CREATE A DOCUMENT

PUT /document/3 => EDIT A DOCUMENT

GET /document/metadata => RETURNS GENERAL CONFIGURATION FOR ALL DOCS

GET /document/6/tags => GETS ALL TAGS FOR DOCUMENT ID=6

POST /document/6/tags => CREATES TAGS FOR DOCUMENT ID=6

I don't even post the WebApi because it's quite embarrassing. Like I said, I was able to make it work with the extra methods but it was almost a one to one mapping to my controller/actions. AWFUL... I know.

When I added a second Controller.... obviously... Mission Impossible.

Now, don't want you to think I haven't done some research (i've used constraints, defaults, etc., etc., to not avail... pitiful). Once I get one Controller to work, the other breaks.

I get all sorts of errors. From "more than one method for GET" to "Resource Not Found", "method now allowed POST".

Here I am, five days later going in circles. This is extremely frustrating.

QUESTION.

Has anyone faced these issues? Is the routing deal SO limiting that you can only have those 4 operations per control? Should I create a route template PER action in my controller, meaning that if I have 10 controllers with 10 actions I should add 100 route templates?

Was it helpful?

Solution

Building on the answer from Yishai, I think changing your routes to the following should work for your scenario:

config.Routes.MapHttpRoute(
    name: "DefaultApi1",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional, action = "Default" },
    constraints: new { id = @"\d*" }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi2",
    routeTemplate: "api/{controller}/{action}",
    defaults: null,
    constraints: new { action = @"[a-zA-Z]+" }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi3",
    routeTemplate: "api/{controller}/{id}/{action}",
    defaults: null,
    constraints: new { id = @"\d+", action = @"[a-zA-Z]+" }
);

Then on your default actions place an ActionName attribute.

public class DocumentController : ApiController
{
    [ActionName("Default")]
    [HttpGet]
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse(HttpStatusCode.OK,"This is GET");
    }

    [HttpGet]
    public HttpResponseMessage Metadata()
    {
        return Request.CreateResponse(HttpStatusCode.OK,"This is Metadata");
    }

    [ActionName("Default")]
    [HttpGet]
    public HttpResponseMessage Get(int id)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is GET with ID");
    }

    [ActionName("Default")]
    [HttpPost]
    public HttpResponseMessage Post([FromBody] DocumentDTO document)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is Post");
    }

    [ActionName("Default")]
    [HttpPut]
    public HttpResponseMessage Put(int id, [FromBody] DocumentDTO document)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is PUT");
    }

    [HttpGet]
    public HttpResponseMessage Tags(int id)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is GET TAGS");
    }

    [HttpPost]
    public HttpResponseMessage Tags(int id, [FromBody] IEnumerable<TagDefinitionDTO> tagDefinitions)
    {
        return Request.CreateResponse(HttpStatusCode.OK, "This is POST TAGS");
    }
}

As mentioned, Web API 2 is a much better way to go.

OTHER TIPS

Yes you can, switch to using attribute routing.

Then on each action or controller you can just put the route template.

OR the following routes should work for you

config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
    name: "DefaultApi1",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: new { id = @"\d+" }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi2",
    routeTemplate: "api/{controller}/{id}/{action}",
    defaults: null,
    constraints: new { id = @"\d+" }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi3",
    routeTemplate: "api/{controller}/{action}",
    defaults: new {id = RouteParameter.Optional}
);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top