How to design a REST API with C# in ASP.NET Core 2

What is REST?

REST is short for Representational State Transfer,  and is way to design web services. So called RESTful web services.

Usually the RESTful service is accessible via a URI and HTTP operations such as  GET, POST, PUT, and DELETE are used to get or modify the data that is often stored in a database behind the web service.

Data being retrieved or sent to the web service is often formatted as JavaScript Object Notation, JSON.

Defining the API

Let’s assume we have a database containing Products that we wish to be able to Create, Read, Update, and Delete (CRUD) using the API. The service that handles the requests will be accessed using HTTP operations as listed below:

URI Operation Description
/products GET Lists all the products
/products/{id} GET Returns information about a specific product
/products POST Creates a new product
/products/{id} PUT Replaces an existing product
/products/{id} DELETE Deletes a product

Note that it does not matter whether the client that will be using the API is a mobile app, a desktop app, a web app, or something else.

Creating the RESTful Service

I am using Visual Studio 2017, Version 15.8.5 and .NET Core 2.1. If you use different versions, things may work a little different.

Start by selecting File -> New -> Project and in the ”New Project” dialog box choose Visual C# -> Web -> ASP.NET Core Web Application. Name the project ”ProductsAPI” and click OK.

 

In the next dialog that pops up, select ’API’ and click OK.

When you use this template, code will be generated that sets up some basic configuration and adds a dummy controller so that you can build and run the application. If you press ’Ctrl + F5’ the application should start and open up a new browser tab displaying ”value1” and ”value2” formatted as JSON.

Implementing the API

Creating a Product model

Let’s start by adding a Product model. Looking at the API definition above we can see that we want the Product to have an Id. It should also have a Name and a Description, so let’s add that as well:

public class Product
{
  public long Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
}

I created a Models folder on project level and added the Product class there.

Creating a ProductContext and a DbSet

Since we are using ASP.NET I want to take advantage of Entity Framework when working with the database. Entity Framework is an Object-Relational Mapper (O/RM) which let’s you work with domain specific objects and don’t have to worry so much about how to interface with the database. The Entity Framework types that we will be using are the DbContext and the DbSet. The DbContext represents the connection to the database and one or more tables. The tables are represented by DbSets.

Confusing? Let’s look at some code:

public class ProductContext : DbContext
{
  public DbSet<Product> Products { get; set; }

  public ProductContext(DbContextOptions<ProductContext> options) : base(options)
  {
  }
}

The code above informs us that we expect to have a database containing a table of Products. The constructor argument is of type DbContextOptions and will contain configuration options for the database connection.

Registering the ProductContext with the Dependency Injection container

In order to make it easy to be able to query and update the database I will register the ProductContext with the Dependency Injection container which is a built in component of ASP.NET Core. Doing that will make it possible to automatically instantiate objects that takes a ProductContext as a constructor parameter. I will use that later on when designing the ProductsController.

When the project was generated from the API template one of the files that were automatically added and populated was the Startup.cs. This class contains a couple of methods that gets called by the runtime during application startup. We will register the ProductContext in the ConfigureServices method by adding this line of code:

services.AddDbContext<ProductContext>(options => options.UseInMemoryDatabase("ProductList"));

This single line registers the ProductContext as a service in the Dependency Injection service collection and specifies an in memory database, named ProductList to be injected into the service container. Not bad for a single line of code, don’t you think?

Creating a controller to handle the HTTP requests

So far we have only created scaffolding, but no implementation to actually handle any HTTP requests. Now it’s time to change that.

Looking back at the API definition we expect that a user should be able to list all the products in the database by a GET request using the URI /products. The most straightforward way to accomplish this is to add a ProductsController, that takes a ProductContext in it’s constructor, and then add a Get action that returns the list of products.

There should be a folder in the projects that is named Controllers. Add a new API Controller to it and name it ProductsController. The code should look like this:

[Route("api/[controller]"]
[ApiController]
public class ProductsController : ControllerBase
{
  private readonly ProductContext _context;
  public ProductsController(ProductContext context)
  {
    _context = context;
    if (_context.Products.Count() == 0)
    {
      _context.Add(new Product
      {
        Name = "Ford Mustang",
        Description = "Classic American car."
      });
      _context.SaveChanges();
    }
  }

  [HttpGet]
  public ActionResult<IEnumerable<Product>> GetAll()
  {
    return _context.Products.ToList();
  }
}

As you can see, I add a new Product, a Ford Mustang, in the case that the database is empty. This is just so that we can see that it works as expected.

Pressing Ctrl+F5 and browsing to http://localhost:<some_port>/api/products should now result in something similar to this:

Implementing the rest of the API is pretty similar. I will just throw the code right at you:

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
  private readonly ProductContext _context;

  public ProductsController(ProductContext context)
  {
    _context = context;
    if (_context.Products.Count() == 0)
    {
      _context.Add(new Product
      {
        Name = "Ford Mustang",
        Description = "Classic American car."
      });
      _context.SaveChanges();
    }
  }

  [HttpGet]
  public ActionResult<IEnumerable<Product>> GetAll()
  {
    return _context.Products.ToList();
  }

  [HttpGet("{id}", Name = "GetProduct")]
  public ActionResult<Product> GetById(long id)
  {
    var p = _context.Products.Find(id);
    if (p == null)
    {
      return NotFound();
    }
    return p;
  }

  [HttpPost]
  public IActionResult Create(Product product)
  {
    _context.Products.Add(product);
    _context.SaveChanges();

    return CreatedAtRoute("GetProduct", new { id = product.Id }, product);
  }

  [HttpPut("{id}")]
  public IActionResult Update(long id, Product product)
  {
    var p = _context.Products.Find(id);
    if (p == null)
    {
      return NotFound();
    }
    p.Name = product.Name;
    p.Description = product.Description;

    _context.Products.Update(p);
    _context.SaveChanges();

    return NoContent();
  }

  [HttpDelete("{id}")]
  public IActionResult Delete(long id)
  {
    var p = _context.Products.Find(id);
    if (p == null)
    {
      return NotFound();
    }

    _context.Products.Remove(p);
    _context.SaveChanges();

    return NoContent();
  }
}

Take some time to look through the code, it should hopefully be quite straight forward to figure out how it works. If you want me to add a working example on GitHub, let me know in the comments.

I you want to test the API during development I recommend a tool like Postman. It is a good tool for sending HTTP requests and checking the responses.

Rulla till toppen