ASP.NET Core: Implementing API Gateway using Ocelot

In this article, we will learn about creating API gateways for ASP.NET Core Applications using Ocelot. In my previous article, I have explained the process of creating resilient microservices where I have explained its need for application development. Microservices architectures are proven as a backbone of modern high availability-based application architectures. Since microservices provide a Techo-Agnostic approach to designing and developing microservices, it's important to make sure that instead of exposing the public endpoints of each microservice to the client the architecture must create an API gateway so that the client will be provided access to all microservices using a single gateway endpoint. 

What is API Gateway?

API Gateway acts as a FRONT-DOOR for all backend services. This provides a single access point for the business logic, and data from the backend services. The API Gateway has the following advantages

  • Isolates the client application from knowing about the actual address of each of the Microservices.
  • Isolates the client application from knowing about the actual application partitioning across various Microservices.
  • Since the API gateway offers a single endpoint for client applications, the client app is free from making multiple frequent calls to fetch data.
  • The client application does not need to know about the actual protocols used by Microservices for internal communication.
Although there are multiple advantages of API Gateway, there are some drawbacks to this design, and they are as follows

  • API Gateway must be designed, developed, deployed, and managed separately.
  • The stability of API Gateway must be taken care of because this is the only endpoint that is known to the client for communications.
  • If the internal Microservices communication is taking time to process the request the response from the Gateway will be delayed.
Figure 1, will provide an idea of the API Gateways in Microservices architecture



Figure 1: API Gateway 

As shown in Figure 1, the API Gateway offers a single-entry point to all actual back-end services against all requests received from the various client applications.

API Gateway implementation using Ocelot

Ocelot is designed to work with ASP.NET Core.  The Ocelot provides a JSON-based configuration to define routes to actual backend Microservices. The sample basic configuration is shown in Listing 1


{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/Read",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 8001
        }
      ],
      "UpstreamPathTemplate": "/api/read",
      "UpstreamHttpMethod": [ "Get","Post" ]
    },
    {
      "DownstreamPathTemplate": "/api/Write",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 8002
        }
      ],
      "UpstreamPathTemplate": "/api/write",
      "UpstreamHttpMethod": [ "Get" ]
    }

  ],
  
  "GobalConfiguration": {
    "BaseUrl": "http://localhost:8000"
  }
}

Listing 1: Sample configuration for routes             

The Route configuration contains the Routes array and the GlobalConfiguration sections. The most important part of GlobalConfiguration is the BaseUrl. This is the URL of the Gateway service host. The Ocelot must know the URL so that it can accept requests from client apps and perform various administrative operations e.g. reading received request headers. The Routes section contains the following parameters in it:

  • DownstreamPathTemplate: This is the API endpoint of the actual backend service.
  • DownstreamScheme: This is the Protocol used to communicate to the actual backend service.
  • DownstreamHostAndPorts: This is an array that has the Host representing the actual host of the backend service, and the Port representing the access point of the actual backend service.
  • UpstreamPathTemplate: This represents the API endpoint URL exposed by the API Gateway to accept requests from client applications.
  • UpstreamHttpMethod: This represents HTTP request methods that are accepted from the client applications by API Gateway.
Figure 2 explains the working of the Ocelot API Gateway for ASP.NET Core API Services.



Figure 2: Ocelot Gateway  


The Implementation

The code for this article is implemented using Microsoft Visual Studio 2022 Enterprise Edition and .NET 7 SDK. 

Step 1: Open Visual Studio 2022 and create a new ASP.NET Core API project and name it ProductService. In this project, we have a Models folder. In this folder add a new class file and name it Product.cs. In this file, add the Product class as shown in Listing 3

public class Product
{
  public int ProductId { get; set; }
  public string? ProductName { get; set; }
  public int Price { get; set; }
  public int CategoryId { get; set; }
}

Listing 3: The Product class
  
In this project, add a new folder and name it as Database. In this folder add a new file and name it Products.cs. In this file, we will add a product class with hard-coded Product data (You can use the database here). This Listing 4 shows the Products data.


public class Products : List<Product> 
{
    public Products()
    {
        Add(new Product() { ProductId=101,ProductName="P1",Price=1000,CategoryId=1});
        Add(new Product() { ProductId = 102, ProductName = "P2", Price = 1000, CategoryId = 2 });
        Add(new Product() { ProductId = 103, ProductName = "P3", Price = 1000, CategoryId = 3 });
        Add(new Product() { ProductId = 104, ProductName = "P4", Price = 1000, CategoryId = 1 });
        Add(new Product() { ProductId = 105, ProductName = "P5", Price = 1000, CategoryId = 2 });
        Add(new Product() { ProductId = 106, ProductName = "P5", Price = 1000, CategoryId = 3 });
    }
}
Listing 4: The Products data 

In this project, add a new folder and name it Services. In this folder, add a new class file and name it ProductRepoSrevice.cs. In this class file, we will add methods for performing Read/Write operations as shown in Listing 5.

 public class ProductRepoService
 {
     Products prds = new Products();

     public IEnumerable<Product> GetProducts() 
     {
         return prds;
     }

     public IEnumerable<Product> AddProduct(Product p)
     {
         prds.Add(p);
         return prds;
     }
 }

Listing 5: The ProductRepoSrevice class

Let's register the ProductRepoService class in the dependency container by adding code as shown in Listing 6 in program.cs

......
builder.Services.AddScoped<ProductRepoService>();
......

Listing 6: The ProductRepoService class registration

 
In the Controllers folder, add a new empty API controller and name it ProductController.cs. In this controller, we will inject ProductRepoService using constructor injection. This controller will also have HTTP methods for performing Read/Write Operations using the PorductRepoSrevice class. The code in Listing 7 shows the ProductController class.


 [Route("api/[controller]")]
 [ApiController]
 public class ProductController : ControllerBase
 {

     ProductRepoService prdServ;

     public ProductController(ProductRepoService s)
     {
         prdServ = s;
     }

     [HttpGet]
     public IActionResult Get() 
     {
         return Ok(prdServ.GetProducts());
     }

     [HttpPost]
     public IActionResult Post(Product c)
     {
         var result = prdServ.AddProduct(c);
         return Ok(result);
     }

 }
Listing 7: The ProductController class
          
Let's expose the ProductService on port 8001 by modifying ProductService project properties as shown in Figure 3



Figure 3: The ProductService exposed on port 8001  
  
Similarly, we will also create a new CategoryService project with Category class, Categories class, CategoryRepoService class, and CategoryController class. To limit the length of this article I am omitting the code for CategoryService class. Expose this service on port 8002 as shown in Figure 3.  The link for the code is provided at the end of this article.  

Step 2: Open another instance of Visual Studio 2022 and create a new ASP.NET Core API Project. Name this project as AppGatewayApp. In this project install the Ocelot NuGet package.
Step 3: In this project, add a new JSON file and name it ocelot.json. In this file, we will add Routes for the ProductService and  CategoryService as shown in Listing 8


{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/Product",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 8001
        }
      ],
      "UpstreamPathTemplate": "/api/product/read",
      "UpstreamHttpMethod": [ "Get","Post" ]
    },
    {
      "DownstreamPathTemplate": "/api/Category",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 8002
        }
      ],
      "UpstreamPathTemplate": "/api/category/read",
      "UpstreamHttpMethod": [ "Get" ]
    }

  ],
  
  "GobalConfiguration": {
    "BaseUrl": "http://localhost:8000"
  }
}

Listing 8: ocelot.json with route                  

Read the JSON carefully. The Gateway BaseUrl is exposed on port 8000. The ProductServive and CategoryService backend services are defined in DownstreamPathTemplate and its corresponding UpstreamPathTemplate are set on api/product/read and api/category/read respectively

Step 4: Let's modify the Program.cs to read the ocelot.json file and also to register the Ocelot service and the Ocelot middleware as shown in Listing 9 (Code is shown in Bold with Blue color)


using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
// THis will Load the App Configuration for Gateway
builder.Services.AddOcelot(builder.Configuration);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
 
app.UseAuthorization();
// Middleware
//HTTP Pipeline
app.UseOcelot().Wait();


app.MapControllers();
 
app.Run();

Listing 9: The registration of the Ocelot service and middleware

Run all three projects. We will be able to access the ProductService and CategoryService on the following URLs using the UpstreamPathTemplate and BaseUrl

http://localhost:8000/api/product/read and http://localhost:8000/api/category/read

You can test this using any REST client e.g. Postman or Advanced REST Client (ARC). The result is shown in Figure 4




Figure 4: The Product Data accessed using Ocelot Gateway


Conclusion: The API Gateway is one of the most important patterns for Microservices-based architecture. This helps to isolate actual Microservices from the Client application.  The code for this article can be downloaded from this link
          

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach