Node.js & ASP.NET Core 1.0: A Usage Comparison - Part 3: Basic Routing

Node.js & ASP.NET Core 1.0: A Usage Comparison - Part 3: Basic Routing

Welcome back to the third part of the series! Don't forget that everything explained in this post can be seen in the open-source GitHub project. The post itself contains important code pieces only.

In this part we take a look at basic routing capabilities. The requirements for that are pretty simple. We need some kind of URL representing one of our Web APIs. Behind the URL a controller is used to aggregate some data.

If you're not familiar with restify or ASP.NET Core 1.0 yet, invest a few minutes and read part 2 which will provide an introduction to those frameworks not ensure you've a common understanding of both.

To start we need to define which URLs we need. In part 1 we defined what our Web API will do:

For the series we are going to build a Customer Web API which can return a list of customers, create a new and delete an existing customer.

As you can see, we need at least three URLs to represent the mentioned functionality:

  • GET api/customer/list: Returns a list of all customers.
  • POST api/customer: Creates a new customer.
  • DELETE api/customer/:id: Deletes a customer identified by an id.

As usual we start with implementing the Node.js part at first.

Node.js

In part 2 we've created a server using restify. Defining routes with it is super easy. Every HTTP verb (GET, POST, ...) is represented by a method. Take care! Only HTTP DELETE is represented by del instead of delete. delete is a reserved word in JavaScript and shouldn't be used for method or variable names.

Customer list

Let’s define our three Web APIs as mentioned above, one after another:

server.get(‘/api/customer/list’, (req, res) => {
	customerService.list()
		.then(
			customerList => res.json(customerList),
			err => res.send(500, err)
		);
});

This Web API returns a list of customers. You notice the parameters req and res. They are provided by restify and give you access to the underlying request (req) and response (res).

The customerService is a service which can access our customer data and provides a Promise based API. For this sample we don’t need to know how it manages the customers. It just does. ;-)

Later in this series we're going to implement it using a database for storing the data. If you are curious, you can take a look at the implementation at GitHub.

Once we've got an answer from customerService we forward it to the client by invoking res.json() and the object we want to return. It will then be converted to JSON and sent back to the client using a HTTP 200 Ok status code.

If we receive an error from our customerService we return a HTTP 500 Internal Server Error together with the error message.

Create happy customers

Let’s move on with creating a new customer:

server.post(‘/api/customer’, (req, res) => {
	customerService.create(req.body.firstName, req.body.lastName)
		.then(
			() => res.send(200),
			err => res.send(500, err)
		);
});

The idea is basically the same. However notice the usage of req.body. It contains the parsed JSON body with information of the customer name. The client sends the following JSON to our Web API:

{
	"firstName": "Manuel",
	"lastName": "Rauber"
}

req.body.firstName will now contain Manuel. req.body.lastName contains Rauber.

This works due to the usage of server.use(restify.bodyParser()) as mentioned in part 2:

We use restify’s bodyParser to automatically convert a JSON body to nice and suitable JavaScript objects, so we can easily use them later.

Remove annoying customers

What do we do with customers we don’t want anymore? Yep, we delete them from our system:

server.del(‘/api/customer/:id’, (req, res) => {
	customerService.remove(req.params.id)
		.then(
			() => res.send(200),
			err => res.send(500, err)
		);
});

Notice the usage of req.params.id. It contains the value of the id-placeholder :id from our URL route. So, if we call /api/customer/1337 then req.params.id has the value 1337.

Take a look here for the controller in action. Now let's compare this to ASP.NET Core 1.0.

ASP.NET Core 1.0

To start with ASP.NET Core 1.0 we create a new controller by creating a new file customerController.cs within a controllers subdirectory of the project root:

[Route("api/[controller]")]
public class CustomerController: Controller 
{
	private ICustomerService _customerService { get; set; }
	
	public Customer(ICustomerService customerService) {
		_customerService = customerService;
	}
}

The [Route] attribute defines our basic entry point for this controller. All routes within are prefixed with api/[controller] whereas [controller] will be replaced with the name of our controller which is Customer. By default the name is convention based. CustomerController becomes Customer and FooController will become Foo, you get the idea.

Then we use a ICustomerService which is basically similar to Node.js' customerService. Additionally we make use of the built-in ASP.NET MVC 6 dependency injection, so we don’t need to fiddle around with service instances here. The interface looks like this:

public interface ICustomerService
{
    IEnumerable<CustomerModel> List();
    void Create(CustomerModel model);
    void Delete(int id);
}

To define routes we need to implement methods within our controller and decorate them with some HTTP verb attributes:

[HttpGet("list")]
public IActionResult List() 
{
	return Ok(_customerService.List());
}

Using [HttpGet] we mark the method that it is accessible via HTTP GET. The parameter list defines the route. Keep in mind, the route is prefixed with the route of the controller. So we end up with api/customer/list here.

IActionResult represents a generic result type of ASP.NET MVC 6 allowing us to use a broad variety of HTTP status code results like Ok() or Created() and so on. Ok() returns a HTTP 200 Ok whereas Created() returns a HTTP 201 Created.

The given parameters will then be sent back as JSON to client. But wait. JSON? We haven’t defined that somewhere. Yes, you are right! That's defined implicitly when creating the template using the generator-aspnet by adding the all ASP.NET MVC 6 server. If you want to know more about that topic, I’ve written a blog post about that.

Ok - Now let’s create the POST and DELETE route:

[HttpPost("")]
public IActionResult Create([FromBody] CustomerModel model)
{
	_customerService.Create(model);
	return Ok();
}

[HttpDelete("{id}")]
public IActoinResult Delete(int id)
{
	_customerService.Delete(id);
	return Ok();
}

It’s as easy as that code snippet! With [HttpPost] we define a new HTTP POST route. Its empty string parameter "" simply tells that the Web API can be called by the controller’s route which is api/customer. The parameter of the Create uses [FromBody]. It tells ASP.NET MVC 6 to read the HTTP request body and transform it into the given model. In this case the CustomerModel, which is defined by a plain old CLR object (POCO):

public class CustomerModel
{
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
}

For the deletion part, we use [HttpDelete]. Its parameter "{id}" defines a placeholder in the URL which will set the method’s int id parameter. Same here as in Node.js: A HTTP DELETE call to api/customer/1337 will fill int id with 1337.

That’s it for the routing part! Look here for the controller in action.

Read the next blog article about Cross-Origin Resource Sharing.