C# | Configure Swagger on api gateway using ocelot in asp.net core application
Introduction
Swagger configuration on API gateway is not as simple as you are configure normal application. You have to configure it in different way. In this article I will create an API gateway using ocelot and asp.net core application and show you how to configure swagger on API gateway.
Tools and technologies used
- Visual Studio 2022
- .NET 6.0
- In Memory Database
- Entity Framework
- ASP.NET Core Web API
- C#
- Ocelot and
- MMLib.SwaggerForOcelot
Implementation
Step 1: Create solution and projects.
- Create a solution name APIGateway
- Add 4 new web api project, name - Catalog.API, Location.API, Ordering.API and BFF.Web in the solution.
Here, BFF.Web project will act as API Gateway.
Step 2: Install nuget packages.
- Install following nuget package in Catalog.API Project
1 2 3
PM> Install-Package Microsoft.EntityFrameworkCore.InMemory PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer PM> Install-Package Microsoft.EntityFrameworkCore.Tools
- Install following nuget package in Ordering.API Project
1 2 3 4
PM> Install-Package Microsoft.EntityFrameworkCore PM> Install-Package Microsoft.EntityFrameworkCore.InMemory PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer PM> Install-Package Microsoft.EntityFrameworkCore.Tools
- Install following nuget packages in BFF.Web Project
1 2 3 4
PM> Install-Package Ocelot PM> Install-Package Ocelot.Provider.Polly PM> Install-Package Ocelot.Cache.CacheManager PM> Install-Package MMLib.SwaggerForOcelot
Step 3: Organize Catalog.API Project
- Create a Product model class in Catalog.API/Model folder
Product.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Catalog.API.Model
{
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int AvailableStock { get; set; }
public int RestockThreshold { get; set; }
}
}
- Create a CatalogContext class in Catalog.API/Db folder
CatalogContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using Catalog.API.Model;
using Microsoft.EntityFrameworkCore;
namespace Catalog.API.Db
{
public class CatalogContext : DbContext
{
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<Product> Products { get; set; }
}
}
- Modify Program.cs file as follows
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Catalog.API.Db;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<CatalogContext>(opt => opt.UseInMemoryDatabase("CatalogDB"));
// 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.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
- Create a conroller class name ProductsController in Catalog.API/Controllers folder
CatalogController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Catalog.API.Db;
using Catalog.API.Model;
namespace Catalog.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly CatalogContext _context;
public ProductsController(CatalogContext context)
{
_context = context;
}
// GET: api/Products
[HttpGet("GetAll")]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
return await _context.Products.ToListAsync();
}
// GET: api/Products/5
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
return product;
}
// PUT: api/Products/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("Edit/{id}")]
public async Task<IActionResult> PutProduct(int id, Product product)
{
if (id != product.Id)
{
return BadRequest();
}
_context.Entry(product).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Products
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost("Add")]
public async Task<ActionResult<Product>> PostProduct(Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return CreatedAtAction("GetProduct", new { id = product.Id }, product);
}
// DELETE: api/Products/5
[HttpDelete("Delete/{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
_context.Products.Remove(product);
await _context.SaveChangesAsync();
return NoContent();
}
private bool ProductExists(int id)
{
return _context.Products.Any(e => e.Id == id);
}
}
}
Step 4: Organize Ordering.API Project
- Create a Order model class in Ordering.API/Model folder
Order.cs
1
2
3
4
5
6
7
8
9
10
11
12
namespace Ordering.API.Models
{
public class Order
{
public int Id { get; set; }
public string Address { get; set; }
public DateTime OrderDate { get; set; }
public string Comments { get; set; }
}
}
- Create a OrderingContext class in Ordering.API/Db folder
OrderingContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.EntityFrameworkCore;
using Ordering.API.Models;
namespace Ordering.API.Db
{
public class OrderingContext : DbContext
{
public OrderingContext(DbContextOptions<OrderingContext> options) : base(options)
{
}
public DbSet<Ordering.API.Models.Order> Order { get; set; }
}
}
- Modify Program.cs file as follows
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using Microsoft.EntityFrameworkCore;
using Ordering.API.Db;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<OrderingContext>(opt => opt.UseInMemoryDatabase("CatalogDB"));
// 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.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
- Create a conroller class name OrdersController in Ordering.API/Controllers folder
OrdersController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Ordering.API.Db;
using Ordering.API.Models;
namespace Ordering.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
private readonly OrderingContext _context;
public OrdersController(OrderingContext context)
{
_context = context;
}
// GET: api/Orders
[HttpGet("GetAll")]
public async Task<ActionResult<IEnumerable<Order>>> GetOrder()
{
return await _context.Order.ToListAsync();
}
// GET: api/Orders/5
[HttpGet("{id}")]
public async Task<ActionResult<Order>> GetOrder(int id)
{
var order = await _context.Order.FindAsync(id);
if (order == null)
{
return NotFound();
}
return order;
}
// PUT: api/Orders/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("Edit/{id}")]
public async Task<IActionResult> PutOrder(int id, Order order)
{
if (id != order.Id)
{
return BadRequest();
}
_context.Entry(order).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrderExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Orders
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost("Add")]
public async Task<ActionResult<Order>> PostOrder(Order order)
{
_context.Order.Add(order);
await _context.SaveChangesAsync();
return CreatedAtAction("GetOrder", new { id = order.Id }, order);
}
// DELETE: api/Orders/5
[HttpDelete("Delete/{id}")]
public async Task<IActionResult> DeleteOrder(int id)
{
var order = await _context.Order.FindAsync(id);
if (order == null)
{
return NotFound();
}
_context.Order.Remove(order);
await _context.SaveChangesAsync();
return NoContent();
}
private bool OrderExists(int id)
{
return _context.Order.Any(e => e.Id == id);
}
}
}
Step 5: Organize Location.API Project
- Create CountriesController in Location.API/Controllers folder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Microsoft.AspNetCore.Mvc;
namespace Location.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CountriesController : ControllerBase
{
[HttpGet("GetAll")]
public IEnumerable<string> Get()
{
return new string[] {"America","Bangladesh", "Canada" };
}
}
}
Step 6: Organize BFF.Web (API Gateway) Project
- Create a folder name Routes and add the following files in that folder
ocelot.catalog.api.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"Routes": [
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "https",
"SwaggerKey": "catalog",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": "7282"
}
],
"UpstreamPathTemplate": "/catalog/{everything}",
"UpstreamHttpMethod": [
"GET",
"POST",
"PUT",
"DELETE"
]
}
]
}
ocelot.location.api.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"Routes": [
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "https",
"SwaggerKey": "location",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": "7003"
}
],
"UpstreamPathTemplate": "/location/{everything}",
"UpstreamHttpMethod": [
"GET",
"POST",
"PUT",
"DELETE"
]
}
]
}
ocelot.ordering.api.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"Routes": [
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "https",
"SwaggerKey": "ordering",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": "7126"
}
],
"UpstreamPathTemplate": "/ordering/{everything}",
"UpstreamHttpMethod": [
"GET",
"POST",
"PUT",
"DELETE"
]
}
]
}
ocelot.global.json
1
2
3
4
5
{
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5205"
}
}
ocelot.SwaggerEndPoints.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"SwaggerEndPoints": [
{
"Key": "bffweb",
"TransformByOcelotConfig": false,
"Config": [
{
"Name": "BFF.Web",
"Version": "1.0",
"Url": "http://localhost:5205/swagger/v1/swagger.json"
}
]
},
{
"Key": "location",
"TransformByOcelotConfig": true,
"Config": [
{
"Name": "Location.API",
"Version": "1.0",
"Url": "http://localhost:5205/location/swagger/v1/swagger.json"
}
]
},
{
"Key": "catalog",
"TransformByOcelotConfig": true,
"Config": [
{
"Name": "Catalog.API",
"Version": "1.0",
"Url": "http://localhost:5205/catalog/swagger/v1/swagger.json"
}
]
},
{
"Key": "ordering",
"TransformByOcelotConfig": true,
"Config": [
{
"Name": "Ordering.API",
"Version": "1.0",
"Url": "http://localhost:5205/catalog/swagger/v1/swagger.json"
}
]
}
]
}
- Add AlterUpstream Class in Config Folder
AlterUpstream.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BFF.Web.Config
{
public class AlterUpstream
{
public static string AlterUpstreamSwaggerJson(HttpContext context, string swaggerJson)
{
var swagger = JObject.Parse(swaggerJson);
// ... alter upstream json
return swagger.ToString(Formatting.Indented);
}
}
}
- Modify Program.cs file as follows
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using BFF.Web.Config;
using MMLib.SwaggerForOcelot.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Polly;
var builder = WebApplication.CreateBuilder(args);
var routes = "Routes";
builder.Configuration.AddOcelotWithSwaggerSupport(options =>
{
options.Folder = routes;
});
builder.Services.AddOcelot(builder.Configuration).AddPolly();
builder.Services.AddSwaggerForOcelot(builder.Configuration);
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddOcelot(routes, builder.Environment)
.AddEnvironmentVariables();
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// Swagger for ocelot
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseSwaggerForOcelotUI(options =>
{
options.PathToSwaggerGenerator = "/swagger/docs";
options.ReConfigureUpstreamSwaggerJson = AlterUpstream.AlterUpstreamSwaggerJson;
}).UseOcelot().Wait();
app.MapControllers();
app.Run();
Step 7: Run and Test application
- Now run multiple (all) projects and test application using postman.
- Check all end point using api gateway and swagger using the following URL
https://localhost:7205/swagger/index.html
- Select Swagger definition from top right corner of BFF