Building a RESTful Web API with ASP.NET Core
Tags: api, dotnet, aspdotnet, dotnetcore
Published: 2023-11-02 13:06:38

REST (Representational State Transfer) has become the standard for building web APIs that are consumed by a wide variety of clients. ASP.NET Core includes great support for quickly building RESTful web APIs. In this post, we'll look at how to build a basic REST API with ASP.NET Core.

## Getting Started
First, let's look at how to create a new ASP.NET Core web API project. You can use either the .NET Core CLI or Visual Studio to generate the boilerplate code.

## Via .NET Core CLI
Open a command prompt or terminal and run:

```cmd
dotnet new webapi -o MyWebAPI
```
It will create a new folder called MyWebAPI with a basic ASP.NET Core web API project set up.

Navigate into this new folder and run dotnet run to build and run the web API. It will be hosted at http://localhost:5000 by default. You'll see a basic "weather forecast" API with some fake sample data.

## Via Visual Studio
In Visual Studio, select File > New Project and choose the ASP.NET Core Web Application template. Select the API project type and click Create.

It will generate a new solution with a Web API project using the same template as we saw earlier with the .NET CLI. You can run this project directly within Visual Studio to launch the API.

## Adding a RESTful Controller
The template includes a WeatherForecastController with some demo methods. Let's build our own proper RESTful controller from scratch.

Right-click on the Controllers folder and choose Add > New Item. Select the API Controller Class template and name it ProductsController.

Delete the existing demo controller, so we're starting fresh.

Our products API will support basic CRUD (create, read, update, delete) operations for managing a collection of products. Let's start with the read operations.

## Get All Products
We'll need a way for clients to request a list of all products. In a RESTful API, this is mapped to an HTTP GET request to the base collection resource endpoint.

Add this method to the ProductsController class:

```csharp
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll()
{
  // Todo: retrieve list of products
  return Ok(products);
}
```
Let's go over what's happening here:

- The [HttpGet] attribute decorates this action method, indicating it responds to GET requests.
- The method name GetAll is a semantic name describing this action.
- It returns an IEnumerable<Product> to represent a collection of products.
- The Ok() method returns a 200 OK response with the products in the body.

Later on we'll actually fetch data from a database, but for now we can fake it by returning a hard-coded list of products:

```csharp
var products = new List<Product>
{
  new Product { Id = 1, Name = "Apple" },
  new Product { Id = 2, Name = "Orange" },
  new Product { Id = 3, Name = "Banana" }
};

return Ok(products);
```
With this simple method in place, clients can now call GET /api/products to retrieve the collection of products.

## Get Product By Id
We also need a way to fetch a single product by its unique id. It will respond to a GET request to /api/products/{id}:

```csharp
[HttpGet("{id}")]
public ActionResult<Product> GetById(int id)
{
  // Todo: get product by id
  
  return Ok(product);
}
```
The {id} token in the route indicates this will accept the id as a parameter from the URL. We can access it in our method via the id parameter.

Again for now we'll fake the data lookup:

```csharp
var product = products.FirstOrDefault(p => p.Id == id);

if (product == null)
  return NotFound();
  
return Ok(product);
```
It will return a 404 Not Found response if an invalid ID is passed in.

## Search Products
Let's add one more read API - searching products by name. We'll map this to GET /api/products/search/{name}.

The request will accept a query string parameter for the search term to match against product names.

```csharp
[HttpGet("search/{name}")]
public ActionResult<IEnumerable<Product>> SearchByName(string name) 
{
  var matchedProducts = products.Where(p => p.Name.Contains(name));
  
  return Ok(matchedProducts);
}
```
It implements a simple search by filtering products that contain the given search term in their name.

With these three methods, we've implemented GET operations to fetch products in different ways.

## Adding POST, PUT, and DELETE Methods
Now, let's implement the create, update, and delete parts of CRUD.

## Create Product
To create a new product, we'll expect clients to send a POST request to /api/products with the new product data in the request body as JSON.

Add this method to the controller:

```csharp
[HttpPost]
public ActionResult<Product> CreateProduct(Product product)  
{
  // Todo: save product
  
  return CreatedAtAction(nameof(GetById), new { id = product.Id }, product); 
}
```
A few things to note:

- The [HttpPost] attribute maps this to POST requests.
- ASP.NET will automatically deserialize the JSON body into a Product object that gets passed in.
- We return a 201 Created response with the CreatedAtAction helper. It includes the URI of the newly created resource in the Location header.

For now, we can fake persisting the data by just adding to our in-memory collection:

```charp
products.Add(product); 

return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
```

## Update Product
To update an existing product, we'll expect clients to send a PUT request to the URI of that resource /api/products/{id}.

The product data to update will be in the request body.

Here is the method:

```csharp
[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, Product product)
{
  // Todo: update product
  
  return NoContent();
}
```
Again ASP.NET will deserialize the body to a Product object for us. We also get the id from the route.

To update, we can find the existing product and modify its properties:

```csharp
var existingProduct = products.FirstOrDefault(p => p.Id == id);

if (existingProduct == null) 
  return NotFound();
  
existingProduct.Name = product.Name; 

return NoContent();
```
It returns a 204 No Content response on success per REST standards.

## Delete Product
Finally, we need to be able to delete a product by id. We'll expose it as a DELETE request to /api/products/{id}.

Here is the delete method:

```code
[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id)  
{
  // Todo: delete product
  
  return NoContent();
}
```
We can find and remove the product from our list with:

```code
var product = products.FirstOrDefault(p => p.Id == id);

if (product == null) 
  return NotFound();

products.Remove(product);
  
return NoContent();
```
It will return a 404 if the given id doesn't exist. Otherwise, we remove the product and return 204 No Content.

And that covers the complete set of CRUD operations for managing products!

**Let's review what we've built:**

- A RESTful ASP.NET Core web API project
- A ProductsController with GET, POST, PUT, and DELETE methods
- Methods to handle requets for creating, retrieving, updating, and deleting products
- Returning appropriate HTTP status codes
It provides the foundation for a fully-functional, production-ready API.

**Here are some ways you can build on top of it:**

- Connect it to a persistent database using EF Core
- Implement authentication and authorization
- Add validation to enforce business rules
- Add more advanced query features for filtering, sorting, pagination
- Set up API documentation with Swagger/OpenAPI
- Containerize it with Docker for easy deployment
ASP.NET Core provides many built-in tools to incorporate these kinds of enterprise-level features as you build on your API.

**Read: [Build ASP.NET Core Web API with Entity Framework Core](https://positiwise.com/blog/a-guide-to-building-web-apis-with-entity-framework-core-in-net-7)**