WEB API – Gateway with Ocelot

In this post, we will see how to create an API Gateway using Ocelot.

First of all, why should we use an API Gateway?
From Microsoft web site:
In a microservices architecture, the client apps usually need to consume functionality from more than one microservice.
If that consumption is performed directly, the client needs to handle
multiple calls to microservice endpoints. What happens when the application evolves and new microservices are introduced or existing microservices are updated? If your application has many microservices, handling so many endpoints from the client apps can be a nightmare. Since the client app would be coupled to those internal endpoints, evolving the microservices in the future can cause high impact for the client apps.
Therefore, having an intermediate level or tier of indirection (Gateway) can be very convenient for microservice-based applications. If you don’t have API Gateways, the client apps must send requests directly to the microservices and that raises many problems.

Briefly an API gateway is an entry point for all our services. This allow us to have a single domain for all of our APIs, rather than having a subdomain for each of them. With the subdomains we have the concern about SSL for each API, frontend connecting to several APIs generating more effort to deal with authorization, among others.
Ocelot is an open source API gateway framework for .NET.


We open Visual Studio 2019 and we create a Blank Solution called BookShopMicroservices.
Then, we add two ASP.NET Core Web Application (API) called Api.Books and Api.Customers that they will be our two Microservices:


First of all, we have to install in every Microservices these four libraries:
AutoMapper
AutoMapper.Extensions.Microsoft.DependencyInjection
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory


After the installation, we can start to define our Microservices:


API.BOOKS


DB: We define an Entity called Book and a DbContext called BookDbContext:

namespace Api.Books.Db
{
    public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public decimal Price { get; set; }
    }
}


using Microsoft.EntityFrameworkCore;

namespace Api.Books.Db
{
    public class BookDbContext:DbContext
    {
        public DbSet<Book> Books { get; set; }
        public BookDbContext(DbContextOptions<BookDbContext> options) : base(options)
        {
        }
    }
}


REPOSITORY: We define a generic repository called GenericRepository, a book repository called BookRepository and the UnitOfWork.
Obviously, we will define the Interfaces first:

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Books.Core.Interface
{
    public interface IGenericRepository<TEntity> where TEntity : class
    {
        Task<IEnumerable<TEntity>> GetAll();
        Task<TEntity> GetById(int id);
        Task<bool> Create(TEntity entity);
    }
}


using Api.Books.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Books.Core.Interface
{
    public interface IBookRepository
    {
        Task<bool> InsertBook(Book objBook);
        Task<IEnumerable<Book>> GetAllBooks();
        Task<Book> GetBookById(int id);
    }
}


using Api.Books.Core.Interface;
using System;
using System.Threading.Tasks;

namespace Api.Books.Core.Interfaces
{
    public interface IUnitOfWork : IDisposable
    {
        IBookRepository Books { get; }
        Task Save();
    }
}


using Api.Books.Core.Interface;
using Api.Books.Db;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Books.Core.Providers
{
    public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        private readonly BookDbContext context;

        public GenericRepository(BookDbContext dbContext)
        {
            this.context = dbContext;
        }
        public async Task<bool> Create(TEntity entity)
        {
            try
            {
                await context.Set<TEntity>().AddAsync(entity);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public async Task<IEnumerable<TEntity>> GetAll()
        {
            return await context.Set<TEntity>().AsNoTracking().ToListAsync();
        }

        public async Task<TEntity> GetById(int id)
        {
            return await context.Set<TEntity>().FindAsync(id);
        }
    }
}


using Api.Books.Core.Interface;
using Api.Books.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Books.Core.Providers
{
    public class BookRepository : GenericRepository<Book>, IBookRepository
    {
        private readonly BookDbContext context;

        public BookRepository(BookDbContext dbContext) : base(dbContext)
        {
            this.context = dbContext;
        }

        public async Task<IEnumerable<Book>> GetAllBooks()
        {
            return await GetAll();
        }

        public async Task<Book> GetBookById(int id)
        {
            return await GetById(id);
        }

        public async Task<bool> InsertBook(Book objBook)
        {
            return await Create(objBook);
        }
    }
}


using Api.Books.Core.Interface;
using Api.Books.Core.Interfaces;
using Api.Books.Db;
using System.Threading.Tasks;

namespace Api.Books.Core.Providers
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly BookDbContext _context;

        public UnitOfWork(BookDbContext context)
        {
            _context = context;
            Books = new BookRepository(_context);
        }

        public IBookRepository Books { get; private set; }

        public async Task Save()
        {
            await _context.SaveChangesAsync();
        }

        public void Dispose()
        {
            _context.Dispose();
        }
    }
}



MODEL UI: We define an entity called BookUI, used as output in the Web API:

namespace Api.Books.Core.Model
{
    public class BookUI
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public decimal Price { get; set; }
    }
}


MAPPER: We create a class called BookMapper, used to create the map between the entities Book and BookUI:

using Api.Books.Core.Model;
using Api.Books.Db;

namespace Api.Books.Core.Mapper
{
    public class BookMapper:AutoMapper.Profile
    {
        public BookMapper()
        {
            CreateMap<Book, BookUI>();
        }
    }
}



CORE: We define a class called CoreBook, where we define all functionality that we will use in the Web API:

using Api.Books.Core.Model;
using Api.Books.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Books.Core.Interfaces
{
    public interface ICoreBook
    {
        Task<(bool IsSuccess, IEnumerable<BookUI> Books, string ErrorMessage)> GetBooksAsync();
        Task<(bool IsSuccess, BookUI Book, string ErrorMessage)> GetBookAsync(int id);
        Task<(bool IsSuccess, string ErrorMessage)> AddBookAsync(Book objBook);
    }
}


using Api.Books.Core.Interfaces;
using Api.Books.Core.Model;
using Api.Books.Db;
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Api.Books.Core.Providers
{
    public class CoreBook : ICoreBook
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IMapper _mapper;

        public CoreBook(IUnitOfWork inputUnitOfWork, IMapper mapper)
        {
            _unitOfWork = inputUnitOfWork;
            _mapper = mapper;
            Task.Run(() => FeedDbInMemory()).Wait();
        }

        private async Task FeedDbInMemory()
        {
            var lstBooks = await _unitOfWork.Books.GetAllBooks();
            if (!lstBooks.Any())
            {
                var book1 = new Book { Id = 1, Title = "Twilight", Price = 10 };
                var book2 = new Book { Id = 2, Title = "Treasure Island", Price = 12 };
                var book3 = new Book { Id = 3, Title = "The God of Small Things", Price = 11 };
                var book4 = new Book { Id = 4, Title = "Pride & Prejudice", Price = 16 };
                var book5 = new Book { Id = 5, Title = "A Fine Balance", Price = 13 };
                var book6 = new Book { Id = 6, Title = "The Lord of the Rings", Price = 12 };
                var book7 = new Book { Id = 7, Title = "Harry Potter series", Price = 11 };
                var book8 = new Book { Id = 8, Title = "Infinite", Price = 10 };
                var book9 = new Book { Id = 9, Title = "Stranger in a Strange Land", Price = 10 };
                var book10 = new Book { Id = 10, Title = "The Color Purple", Price = 9 };

                await _unitOfWork.Books.InsertBook(book1);
                await _unitOfWork.Books.InsertBook(book2);
                await _unitOfWork.Books.InsertBook(book3);
                await _unitOfWork.Books.InsertBook(book4);
                await _unitOfWork.Books.InsertBook(book5);
                await _unitOfWork.Books.InsertBook(book6);
                await _unitOfWork.Books.InsertBook(book7);
                await _unitOfWork.Books.InsertBook(book8);
                await _unitOfWork.Books.InsertBook(book9);
                await _unitOfWork.Books.InsertBook(book10);

                await _unitOfWork.Save();
            }
        }
        public async Task<(bool IsSuccess, string ErrorMessage)> AddBookAsync(Book objBook)
        {
            try
            {
                var insertedBook = await _unitOfWork.Books.InsertBook(objBook);
                await _unitOfWork.Save();

                if (insertedBook)
                {
                    return (true, null);
                }
                return (false, "Not found");
            }
            catch (Exception ex)
            {
                return (false, ex.Message);
            }
        }

        public async Task<(bool IsSuccess, BookUI Book, string ErrorMessage)> GetBookAsync(int id)
        {
            try
            {
                var objBook = await _unitOfWork.Books.GetBookById(id);
                if (objBook != null)
                {
                    return (true, _mapper.Map<BookUI>(objBook), null);
                }
                return (false, null, "Not found");
            }
            catch (Exception ex)
            {
                return (false, null, ex.Message);
            }
        }

        public async Task<(bool IsSuccess, IEnumerable<BookUI> Books, string ErrorMessage)> GetBooksAsync()
        {
            try
            {
                var lstBooks = await _unitOfWork.Books.GetAllBooks();
                if (lstBooks != null && lstBooks.Any())
                {
                    return (true, _mapper.Map<IEnumerable<BookUI>>(lstBooks), null);
                }
                return (false, null, "Not found");
            }
            catch (Exception ex)
            {
                return (false, null, ex.Message);
            }
        }

        public void Dispose()
        {
            _unitOfWork.Dispose();
        }
    }
}



API CONTROLLER: We create a controller called BooksController, where we will define the Microservices’ functionalities:

using Api.Books.Core.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace Api.Books.Controllers
{
    [Route("api/books")]
    [ApiController]
    public class BooksController : ControllerBase
    {
        private readonly ICoreBook coreBook;

        public BooksController(ICoreBook coreBook)
        {
            this.coreBook = coreBook;
        }

        [HttpGet]
        public async Task<IActionResult> GetBooksAsync()
        {
            var lstBooks = await coreBook.GetBooksAsync();
            if (lstBooks.IsSuccess)
            {
                return Ok(lstBooks.Books);
            }
            return NotFound(lstBooks.ErrorMessage);
        }

        [HttpGet("{bookId}")]
        public async Task<IActionResult> GetBookAsync(int bookId)
        {
            var objBook = await coreBook.GetBookAsync(bookId);
            if (objBook.IsSuccess)
            {
                return Ok(objBook.Book);
            }
            return NotFound();
        }
    }
}


Finally, we modify the file Startup.cs, in order to define the dependency injection and for the definition of DbContext:

using Api.Books.Core.Interface;
using Api.Books.Core.Interfaces;
using Api.Books.Core.Providers;
using Api.Books.Db;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Api.Books
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IBookRepository, BookRepository>();
            services.AddScoped<ICoreBook, CoreBook>();
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            services.AddAutoMapper(typeof(Startup));
            services.AddDbContext<BookDbContext>(options =>
                options.UseInMemoryDatabase("Books"));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}


Now, using Postman, we will check the Microservices:



It works fine and now, we will define the second Microservices called API.CUSTOMERS:

API.CUSTOMERS


DB: We define an Entity called Customer and a DbContext called CustomerDbContext:

namespace Api.Customers.Db
{
    public class Customer
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
    }
}


using Microsoft.EntityFrameworkCore;

namespace Api.Customers.Db
{
    public class CustomerDbContext:DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public CustomerDbContext(DbContextOptions<CustomerDbContext> options) : base(options)
        {
        }
    }
}


REPOSITORY: We define a generic repository called GenericRepository, a customer repository called CustomerRepository and the UnitOfWork.
Obviously, we will define the Interfaces first:

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Customers.Core.Interfaces
{
    public interface IGenericRepository<TEntity> where TEntity : class
    {
        Task<IEnumerable<TEntity>> GetAll();
        Task<TEntity> GetById(int id);
        Task<bool> Create(TEntity entity);
    }
}


using Api.Customers.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Customers.Core.Interfaces
{
    public interface ICustomerRepository
    {
        Task<bool> InsertCustomer(Customer objCustomer);
        Task<IEnumerable<Customer>> GetAllCustomers();
        Task<Customer> GetCustomerById(int id);
    }
}


using System;
using System.Threading.Tasks;

namespace Api.Customers.Core.Interfaces
{
    public interface IUnitOfWork : IDisposable
    {
        ICustomerRepository Customers { get; }
        Task Save();
    }
}


using Api.Customers.Core.Interfaces;
using Api.Customers.Db;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Customers.Core.Repository
{
    public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        private readonly CustomerDbContext context;

        public GenericRepository(CustomerDbContext dbContext)
        {
            this.context = dbContext;
        }
        public async Task<bool> Create(TEntity entity)
        {
            try
            {
                await context.Set<TEntity>().AddAsync(entity);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public async Task<IEnumerable<TEntity>> GetAll()
        {
            return await context.Set<TEntity>().AsNoTracking().ToListAsync();
        }

        public async Task<TEntity> GetById(int id)
        {
            return await context.Set<TEntity>().FindAsync(id);
        }
    }
}


using Api.Customers.Core.Interfaces;
using Api.Customers.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Customers.Core.Repository
{
    public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository
    {
        private readonly CustomerDbContext context;

        public CustomerRepository(CustomerDbContext dbContext) : base(dbContext)
        {
            this.context = dbContext;
        }

        public async Task<IEnumerable<Customer>> GetAllCustomers()
        {
            return await GetAll();
        }

        public async Task<Customer> GetCustomerById(int id)
        {
            return await GetById(id);
        }

        public async Task<bool> InsertCustomer(Customer objCustomer)
        {
            return await Create(objCustomer);
        }
    }
}


using Api.Customers.Core.Interfaces;
using Api.Customers.Db;
using System.Threading.Tasks;

namespace Api.Customers.Core.Repository
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly CustomerDbContext _context;

        public UnitOfWork(CustomerDbContext context)
        {
            _context = context;
            Customers = new CustomerRepository(_context);
        }

        public ICustomerRepository Customers { get; private set; }

        public async Task Save()
        {
            await _context.SaveChangesAsync();
        }

        public void Dispose()
        {
            _context.Dispose();
        }
    }
}



MODEL UI: We define an entity called CustomerUI, used as output in the Web API:

namespace Api.Customers.Core.Model
{
    public class CustomerUI
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
    }
}



MAPPER: We create a class called CustomerMapper, used to create the map between the entities Customer and CustomerUI:

using Api.Customers.Core.Model;
using Api.Customers.Db;

namespace Api.Customers.Core.Mapper
{
    public class CustomerMapper : AutoMapper.Profile
    {
        public CustomerMapper()
        {
            CreateMap<Customer, CustomerUI>();
        }
    }
}



CORE: We define a class called CoreCustomer, where we define all functionality that we will use in the Web API:

using Api.Customers.Core.Model;
using Api.Customers.Db;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Api.Customers.Core.Interfaces
{
    public interface ICoreCustomer
    {
        Task<(bool IsSuccess, IEnumerable<CustomerUI> Customers, string ErrorMessage)> GetCustomersAsync();
        Task<(bool IsSuccess, CustomerUI Customer, string ErrorMessage)> GetCustomerAsync(int id);
        Task<(bool IsSuccess, string ErrorMessage)> AddCustomerAsync(Customer objCustomer);
    }
}


using Api.Customers.Core.Interfaces;
using Api.Customers.Core.Model;
using Api.Customers.Db;
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Api.Customers.Core.Providers
{
    public class CoreCustomer : ICoreCustomer
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IMapper _mapper;

        public CoreCustomer(IUnitOfWork inputUnitOfWork, IMapper mapper)
        {
            _unitOfWork = inputUnitOfWork;
            _mapper = mapper;
            Task.Run(() => FeedDbInMemory()).Wait();
        }

        private async Task FeedDbInMemory()
        {
            var lstCustomers = await _unitOfWork.Customers.GetAllCustomers();
            if (!lstCustomers.Any())
            {
                for(int i=1;i<=10;i++)
                {
                    var objCustomer = new Customer { Id = i, UserName = $"User{i}", Email=$"user{i}@email.co.uk" };
                    await _unitOfWork.Customers.InsertCustomer(objCustomer);
                }

                await _unitOfWork.Save();
            }
        }
        public async Task<(bool IsSuccess, string ErrorMessage)> AddCustomerAsync(Customer objCustomer)
        {
            try
            {
                var insertedCustomer = await _unitOfWork.Customers.InsertCustomer(objCustomer);
                await _unitOfWork.Save();

                if (insertedCustomer)
                {
                    return (true, null);
                }
                return (false, "Not found");
            }
            catch (Exception ex)
            {
                return (false, ex.Message);
            }
        }

        public async Task<(bool IsSuccess, CustomerUI Customer, string ErrorMessage)> GetCustomerAsync(int id)
        {
            try
            {
                var objCustomer = await _unitOfWork.Customers.GetCustomerById(id);
                if (objCustomer != null)
                {
                    return (true, _mapper.Map<CustomerUI>(objCustomer), null);
                }
                return (false, null, "Not found");
            }
            catch (Exception ex)
            {
                return (false, null, ex.Message);
            }
        }

        public async Task<(bool IsSuccess, IEnumerable<CustomerUI> Customers, string ErrorMessage)> GetCustomersAsync()
        {
            try
            {
                var lstCustomers = await _unitOfWork.Customers.GetAllCustomers();
                if (lstCustomers != null && lstCustomers.Any())
                {
                    return (true, _mapper.Map<IEnumerable<CustomerUI>>(lstCustomers), null);
                }
                return (false, null, "Not found");
            }
            catch (Exception ex)
            {
                return (false, null, ex.Message);
            }
        }

        public void Dispose()
        {
            _unitOfWork.Dispose();
        }
    }
}



API CONTROLLER: We create a controller called CustomerController, where we will define the Microservices’ functionalities:

using Api.Customers.Core.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace Api.Customers.Controllers
{
    [Route("api/customers")]
    [ApiController]
    public class CustomersController : ControllerBase
    {
        private readonly ICoreCustomer coreCustomer;

        public CustomersController(ICoreCustomer coreCustomer)
        {
            this.coreCustomer = coreCustomer;
        }

        [HttpGet]
        public async Task<IActionResult> GetCustomersAsync()
        {
            var lstCustomers = await coreCustomer.GetCustomersAsync();
            if (lstCustomers.IsSuccess)
            {
                return Ok(lstCustomers.Customers);
            }
            return NotFound(lstCustomers.ErrorMessage);
        }

        [HttpGet("{customerId}")]
        public async Task<IActionResult> GetCustomerAsync(int customerId)
        {
            var objCustomer = await coreCustomer.GetCustomerAsync(customerId);
            if (objCustomer.IsSuccess)
            {
                return Ok(objCustomer.Customer);
            }
            return NotFound();
        }
    }
}


Finally, we will modify the file Startup.cs, in order to define the dependency injection and for the definition of DbContext:

using Api.Customers.Core.Interfaces;
using Api.Customers.Core.Providers;
using Api.Customers.Core.Repository;
using Api.Customers.Db;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Api.Customers
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<ICustomerRepository, CustomerRepository>();
            services.AddScoped<ICoreCustomer, CoreCustomer>();
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            services.AddAutoMapper(typeof(Startup));
            services.AddDbContext<CustomerDbContext>(options =>
                options.UseInMemoryDatabase("Customers"));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}


Now, using Postman, we will check the Microservices:



Perfect! Both the Microservices work fine, but the problem is that a client should know the two different urls in order to use them:

http://localhost:56277/api/Customers
http://localhost:56277/api/Customers/{customerid}

http://localhost:56274/api/Books
http://localhost:56274/api/Books/{bookid}

and so, in situation like that, the introduction of an API Gateway like Ocelot is helpful.



OCELOT

First of all, we create an ASP.NET Core Empty project called Api.Gateway and then we install the Ocelot package:
Install-Package Ocelot

IMPORTANT:
The last stable versions are 16.0.1 and 16.0.0 but they didn’t work so, I had to install the version 15.0.7


Then, we add a json file called ocelot, where we will specify all the API Gateway ReRoutes:

[ocelot.json]

{
  "ReRoutes": 
  [
    {
      "DownstreamPathTemplate": "/api/books",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 56274
        }
      ],
      "UpstreamPathTemplate": "/api/books",
      "UpstreamHttpMethod": [ "GET" ]
    },
    {
      "DownstreamPathTemplate": "/api/books/{id}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 56274
        }
      ],
      "UpstreamPathTemplate": "/api/books/{id}",
      "UpstreamHttpMethod": [ "GET" ]
    },
    {
      "DownstreamPathTemplate": "/api/customers",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 56277
        }
      ],
      "UpstreamPathTemplate": "/api/customers",
      "UpstreamHttpMethod": [ "GET" ]
    },
    {
      "DownstreamPathTemplate": "/api/customers/{id}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 56277
        }
      ],
      "UpstreamPathTemplate": "/api/customers/{id}",
      "UpstreamHttpMethod": [ "GET" ]
    }
  ]
}


Finally, we have to modify the files Program and Startup:

[Program.cs]

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Api.Gateway
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })

            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("ocelot.json");
            });
    }
}


[Startup.cs]

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

namespace Api.Gateway
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOcelot();
        }

        public async void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            await app.UseOcelot();
        }
    }
}



Now, we run all the applications and, using Postman, we will check that the Gateway works fine: