Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC3 RESTful API Routing & Http Verb Handling

I want to build a RESTful Json Api for my MVC3 application. I need help with handling multiple Http Verbs for the manipulation of a single object instance.

What I've read/studied/tried

MVC attributes (HttpGet, HttpPost, etc.) allow me to have a controller with multiple actions sharing the same name, but they still must have different method signatures.

Route constraints happen in the routing module before MVC kicks in and would result in me having 4 explicit routes, and still require individually named controller actions.

ASP.NET MVC AcceptVerbs and registering routes

Building a custom Http Verb Attribute could be used to snatch the verb used to access the action and then pass it as an argument as the action is invoked - the code would then handle switch cases. The issue with this approach is some methods will require authorization which should be handled at the action filter level, not inside the action itself.

http://iwantmymvc.com/rest-service-mvc3


Requirements / Goals

  1. One route signature for a single instance object, MVC is expected to handle the four main Http Verbs: GET, POST, PUT, DELETE.

    context.MapRoute("Api-SingleItem", "items/{id}", 
        new { controller = "Items", action = "Index", id = UrlParameter.Optional }
    );
    
  2. When the URI is not passed an Id parameter, an action must handle POST and PUT.

    public JsonResult Index(Item item) { return new JsonResult(); }
    
  3. When an Id parameter is passed to the URI, a single action should handle GET and DELETE.

    public JsonResult Index(int id) { return new JsonResult(); }
    

Question

How can I have more than one action (sharing the same name and method signature) each respond to a unique http verb. Desired example:

[HttpGet]
public JsonResult Index(int id) { /* _repo.GetItem(id); */}

[HttpDelete]
public JsonResult Index(int id) { /* _repo.DeleteItem(id); */ }

[HttpPost]
public JsonResult Index(Item item) { /* _repo.addItem(id); */}

[HttpPut]
public JsonResult Index(Item item) { /* _repo.updateItem(id); */ }
like image 271
one.beat.consumer Avatar asked Dec 22 '11 22:12

one.beat.consumer


People also ask

What is the difference between Web API routing and MVC routing?

If you are familiar with ASP.NET MVC, Web API routing is very similar to MVC routing. The main difference is that Web API uses the HTTP verb, not the URI path, to select the action. You can also use MVC-style routing in Web API.

What is API routing?

Routing is how Web API matches a URI to an action. Web API 2 supports a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API.

How would you setup the default route using endpoints?

UseEndpoints(endpoints => { endpoints. MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); Inside the call to UseEndpoints() , we use the MapControllerRoute() method to create a route by giving the name default .

Where is the route is defined in Web API?

Attribute routing is supported in Web API 2. As the name implies, attribute routing uses [Route()] attribute to define routes. The Route attribute can be applied on any controller or action method. In order to use attribute routing with Web API, it must be enabled in WebApiConfig by calling config.


1 Answers

For RESTful calls, the action has no meaning, since you want to differ only by HTTP methods. So the trick is to use a static action name, so that the different methods on the controller are only different in the HTTP method they accept.

While the MVC framework provides a solution for specifying action names, it can be made more concise and self-explaining. We solved it like this:

A special attribute is used for specifying RESTful methods (this matches to a special action name):

public sealed class RestfulActionAttribute: ActionNameSelectorAttribute {
    internal const string RestfulActionName = "<<REST>>";

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) {
        return actionName == RestfulActionName;
    }
}

The controllers use it in combination with the HTTP method attributes:

public class MyServiceController: Controller {
    [HttpPost]
    [RestfulAction]
    public ActionResult Create(MyEntity entity) {
        return Json(...);
    }

    [HttpDelete]
    [RestfulAction]
    public ActionResult Delete(Guid id) {
        return Json(...);
    }

    [HttpGet]
    [RestfulAction]
    public ActionResult List() {
        return Json(...);
    }

    [HttpPut]
    [RestfulAction]
    public ActionResult Update(MyEntity entity) {
        return Json(...);
    }
}

And in order to bind those controllers successfully, we use custom routes with the static action name from the beforementionned attribute (which at the same time also allow for customizing the URLs):

routes.MapRoute(controllerName, pathPrefix+controllerName+"/{id}", new {
    controller = controllerName,
    action = RestfulActionAttribute.RestfulActionName,
    id = UrlParameter.Optional
});

Note that all your requirements can be easily met with this approach as far as I can tell; you can have multiple [HttpXxx] attributes on one method to make one method accept multiple HTTP methods. Paired with some smart(er) ModelBinder this is very powerful.

like image 133
Lucero Avatar answered Oct 18 '22 14:10

Lucero