left-icon

ASP.NET Web API Succinctly®
by Emanuele DelBono

Previous
Chapter

of
A
A
A

CHAPTER 4

The Routing System

The Routing System


Basic routing

All HTTP requests pass through the routing system, which decides what will manage the request. The main task of the routing system is to decide which action of which controller should be called to manage the actual request. To make this decision, the routing system parses the HTTP request (in particular, the verb and the URI), and obtains a series of tokens that are matched to a route table containing all the possible routes.

As we saw in Chapter 2, when we create a new Web API application, the template generates one default route for us:

using System.Web.Http;

namespace HelloWebApi

{

    public static class WebApiConfig

    {

        public static void Register(HttpConfiguration config)

        {

            config.Routes.MapHttpRoute(

                name: "DefaultApi",

                routeTemplate: "api/{controller}/{id}",

                defaults: new { id = RouteParameter.Optional }

            );

        }

    }

}

The method MapHttpRoute in this case takes three parameters:

  • A route name (DefaultApi).
  • A route template: A template with a literal (api) and two placeholders (controller and id) that will be replaced with the current request segments.
  • Default values: In this case, we are saying that the id is not mandatory in the request.

If you have used ASP.NET MVC, you will find several similarities. In MVC, however, you should have the action, and here the action is not present. This is the main difference between the two routing systems. In Web API, the action is determined by the HTTP method, as we will see later.

The MapHttpRoute method simply adds a new entry in a dictionary that stores all the routes. The route that we have just defined, given that we have defined a PostsController, will respond to these requests:

  • /api/Posts
  • /api/Posts/42
  • /api/Posts/Syncfusion

If the routing system does not find a correct match, it will return HTTP status 404 Not Found to the caller.

To decide which action should be called, the routing system must analyze the HTTP method. If we place a GET request to the server, the action should be something like GetSomeResource(…). If we place a POST, the action should be something like PostSomeOtherResource(…). The general rule for the default methods is that the action must start with the action name, so if we consider a GET HTTP request then Get(…), GetPosts(…), and GetSomething(…) are all valid actions.

The complete route table for the previous route follows:

A route table

HTTP method

URI

Controller

Action

Parameter

GET

/api/Posts

PostsController

Get()

N/A

GET

/api/Posts/42

PostsController

Get(int id)

42

POST

/api/Posts

PostsController

Post(Post c)

From the body

DELETE

/api/Posts/42

PostsController

Delete(int id)

42

The route that Visual Studio defines for us can be changed and we are not forced to use it, even if it is a general best practice for every REST service.

When modifying or adding new routes, consider that the route table is evaluated in the order that the routes are added. So the first match will be used.

Consider this example:

using System.Web.Http;

namespace HelloWebApi

{

    public static class WebApiConfig

    {

        public static void Register(HttpConfiguration config)

        {

            config.Routes.MapHttpRoute(

                name: "PostByDate",

                routeTemplate: "api/{controller}/{year}/{month}/{day}",

                defaults: new { month = RouteParameter.Optional, day =  RouteParameter.Optional }

            );

            config.Routes.MapHttpRoute(

                name: "DefaultApi",

                routeTemplate: "api/{controller}/{id}",

                defaults: new { id = RouteParameter.Optional }

            );

        }

    }

}

Here we have a new route defined before the default one; this means that the route named PostByDate is evaluated before the other. This new route has four placeholders: one for the controller name, and three to define a date (year, month, and day). In the defaults value parameter, we specify that month and day are optional but year remains mandatory.

To see the example working, we define a new PostsController like this:

using System.Web.Http;

namespace HelloWebApi

{

    public class PostsController : ApiController

    {

        public IQueryable<Post> Get(int year, int month = 0, int day = 0)

        {

            // Do something to load the posts that match the date.

        }

    }

}

The Get method takes three parameters: a year (mandatory), and a month and a day that are optional (if not given, they will contain the default value of the given type, which in our case is zero).

The request will be parsed and decomposed to be able to call the Get action with its parameters:

Request URI and parameters value

URI

Year

Month

Day

GET /api/Posts/2010/04/11

2010

4

11

GET /api/Posts/1977/11

1977

11

0

Get /api/Posts/1973

1973

0

0

As you can see, with this simple route we have defined a REST interface to query posts based on the date, which can be a precise day, a particular month, or a full year.

We have to define two more things to be sure that everything is correct.

First, a route like this is generally used with a single controller. It’s quite strange that every resource could be queried with a date, so it would be better to create a tight link between the route and the controller. This can be done in this way:

// ...

config.Routes.MapHttpRoute(

    name: "PostByDate",

    routeTemplate: "api/Posts/{year}/{month}/{day}",

    defaults: new

        {

            controller = "Posts",

            month = RouteParameter.Optional,

            day = RouteParameter.Optional

        }

    );

// ...

We could simply remove the controller placeholder and use the literal “Posts” so the route will match only if the request contains the posts string (it’s case insensitive).

The other problem is that this route does not impose any constraints on the parameters, so this URI:

/api/Posts/2013/May

is being caught even if it breaks the action, since the month is a string and nothing manages the conversion.

To be sure that the parameters are numbers, we could use a route constraint as in the following code:

// ...

config.Routes.MapHttpRoute(

    name: "PostByDate",

    routeTemplate: "api/Posts/{year}/{month}/{day}",

    defaults: new

        {

            controller = "Posts",

            month = RouteParameter.Optional,

            day = RouteParameter.Optional

        },

    constraints: new

        {

            month = @"\d{0,2}", day = @"\d{0,2}"

        }

    );

// ...

The constraints parameter uses a regular expression to be sure that month and day are two numbers (composed of zero, one, or two digits).

As specified previously, the controller’s action that has to be executed is selected by the HTTP method, so we have only seven actions available. What happens if I want more actions or a custom action name that does not start with Get, Post, and so on?

One way to achieve this behavior is to use the placeholder action in the route definition:

// ...

config.Routes.MapHttpRoute(

    name: "PostsCustomAction",

    routeTemplate: "api/{controller}/{action}/{id}",

    defaults: new { id = RouteParameter.Optional }

    );

// ...

Given this route definition, the action will be explicitly written in the URL, so we are not forced to use the seven default actions. But we can use a URL like:

/api/Posts/Category/10

that will be managed by the action Category in the Posts controller, but this member must have an attribute that specifies the method that it supports:

public class PostsController : ApiController

{

    [HttpGet]

    public string Category(int id)

    {

        // ...

    }

}

The HttpGet attribute is there to tell the runtime that the action Category should be called only against an HTTP GET method.

If we try to POST to this URL we obtain the following error:

The requested resource does not support http method 'POST'

The possible attributes that match all the HTTP methods are:

  • HttpGet
  • HttpPost
  • HttpPut
  • HttpOptions
  • HttpPatch
  • HttpDelete
  • HttpHead

Summary

With all these options, we have all that is needed to define a complete route table that satisfies all our requests. When designing a route table, always keep in mind the readability of the URLs that represent an important part of our user interface.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.