🔖 Implement API versioning
continuous-integration/drone/push Build is passing Details

pull/61/head
Alexis Drai 2 years ago
parent 15bb089e55
commit 179292e87f

@ -3,10 +3,28 @@
"BaseUrl": "https://localhost:5003"
},
"Routes": [
{
"UpstreamPathTemplate": "/gateway/v1/cats",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/api/v1/cats",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7229
}
],
"RateLimitOptions": {
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 1
}
},
{
"UpstreamPathTemplate": "/gateway/cats",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/api/cats",
"DownstreamPathTemplate": "/api/v2/cats",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -24,7 +42,7 @@
{
"UpstreamPathTemplate": "/gateway/cats",
"UpstreamHttpMethod": [ "Post" ],
"DownstreamPathTemplate": "/api/cats",
"DownstreamPathTemplate": "/api/v1/cats",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -36,7 +54,7 @@
{
"UpstreamPathTemplate": "/gateway/cats/{id}",
"UpstreamHttpMethod": [ "Get", "Put", "Delete" ],
"DownstreamPathTemplate": "/api/cats/{id}",
"DownstreamPathTemplate": "/api/v1/cats/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -49,7 +67,7 @@
{
"UpstreamPathTemplate": "/gateway/bars",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/api/bars",
"DownstreamPathTemplate": "/api/v1/bars",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -67,7 +85,7 @@
{
"UpstreamPathTemplate": "/gateway/bars",
"UpstreamHttpMethod": [ "Post" ],
"DownstreamPathTemplate": "/api/bars",
"DownstreamPathTemplate": "/api/v1/bars",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -79,7 +97,7 @@
{
"UpstreamPathTemplate": "/gateway/bars/{id}",
"UpstreamHttpMethod": [ "Get", "Put", "Delete" ],
"DownstreamPathTemplate": "/api/bars/{id}",
"DownstreamPathTemplate": "/api/v1/bars/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -92,7 +110,7 @@
{
"UpstreamPathTemplate": "/gateway/customers",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/api/customers",
"DownstreamPathTemplate": "/api/v1/customers",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -107,7 +125,7 @@
{
"UpstreamPathTemplate": "/gateway/customers",
"UpstreamHttpMethod": [ "Post" ],
"DownstreamPathTemplate": "/api/customers",
"DownstreamPathTemplate": "/api/v1/customers",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
@ -119,7 +137,7 @@
{
"UpstreamPathTemplate": "/gateway/customers/{id}",
"UpstreamHttpMethod": [ "Get", "Put", "Delete" ],
"DownstreamPathTemplate": "/api/customers/{id}",
"DownstreamPathTemplate": "/api/v1/customers/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{

@ -1,22 +1,12 @@
using AutoMapper;
using Castle.Core.Logging;
using cat_cafe.Controllers;
using cat_cafe.Dto;
using cat_cafe.Entities;
using cat_cafe.Mappers;
using cat_cafe.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using cat_cafe.WeSo;
@ -85,10 +75,10 @@ namespace cat_cafe.Controllers.Tests
}
[TestMethod()]
public async Task GetCatsTest()
public async Task GetCatsV2Test()
{
// control response type
var actual = await controller.GetCats();
var actual = await controller.GetCatsV2();
actual.Result.Should().BeOfType<OkObjectResult>();
// control response object

@ -14,8 +14,9 @@ using System.Xml.Linq;
namespace cat_cafe.Controllers
{
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("2.0")]
public class BarsController : ControllerBase
{
private readonly CatCafeContext _context;
@ -29,8 +30,9 @@ namespace cat_cafe.Controllers
_logger = logger;
}
// GET: api/Bars
// GET: api/v1/Bars
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<BarDto>>> GetBars()
{
var bars = _context.Bars
@ -45,8 +47,9 @@ namespace cat_cafe.Controllers
return _mapper.Map<List<BarDto>>(bars);
}
// GET: api/Bars/5
// GET: api/v1/Bars/5
[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public async Task<ActionResult<BarDto>> GetBar(long id)
{
var bar = _context.Bars.Include(p => p.cats)
@ -66,9 +69,10 @@ namespace cat_cafe.Controllers
return _mapper.Map<BarDto>(bar.Result);
}
// PUT: api/Bars/5
// PUT: api/v1/Bars/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> PutBar(long id, BarDto barDto)
{
if (id != barDto.Id)
@ -97,9 +101,10 @@ namespace cat_cafe.Controllers
return NoContent();
}
// POST: api/Bars
// POST: api/v1/Bars
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
[MapToApiVersion("1.0")]
public async Task<ActionResult<BarDto>> PostBar(BarDto barDto)
{
// Bar bar = _mapper.Map<Bar>(barDto);
@ -112,8 +117,9 @@ namespace cat_cafe.Controllers
return CreatedAtAction("GetBar", new { id = barDto.Id }, _mapper.Map<BarDto>(bar));
}
// DELETE: api/Bars/5
// DELETE: api/v1/Bars/5
[HttpDelete("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> DeleteBar(long id)
{
var bar = await _context.Bars.FindAsync(id);

@ -16,8 +16,10 @@ using cat_cafe.WeSo;
namespace cat_cafe.Controllers
{
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class CatsController : ControllerBase
{
private readonly CatCafeContext _context;
@ -38,17 +40,32 @@ namespace cat_cafe.Controllers
_webSocketHandler = webSocketHandler;
}
// GET: api/Cats
// GET: api/v1/Cats
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<CatDto>>> GetCats()
{
var cats = await _context.Cats.ToListAsync();
cats.Add(new Cat { Id = -1, Age = 42, Name = "Hi! I'm the secret V1 cat" });
return Ok(_mapper.Map<List<CatDto>>(cats));
}
// GET: api/v2/Cats
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<ActionResult<IEnumerable<CatDto>>> GetCatsV2()
{
var cats = await _context.Cats.ToListAsync();
return Ok(_mapper.Map<List<CatDto>>(cats));
}
// GET: api/Cats/5
// GET: api/v1/Cats/5
[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public async Task<ActionResult<CatDto>> GetCat(long id)
{
var cat = await _context.Cats.FindAsync(id);
@ -61,9 +78,10 @@ namespace cat_cafe.Controllers
return Ok(_mapper.Map<CatDto>(cat));
}
// PUT: api/Cats/5
// PUT: api/v1/Cats/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> PutCat(long id, CatDto catDto)
{
if (id != catDto.Id)
@ -93,9 +111,10 @@ namespace cat_cafe.Controllers
return NoContent();
}
// POST: api/Cats
// POST: api/v1/Cats
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
[MapToApiVersion("1.0")]
public async Task<ActionResult<CatDto>> PostCat(CatDto catDto)
{
Cat cat = _mapper.Map<Cat>(catDto);
@ -107,8 +126,9 @@ namespace cat_cafe.Controllers
return CreatedAtAction("GetCat", new { id = catDto.Id }, _mapper.Map<CatDto>(cat));
}
// DELETE: api/Cats/5
// DELETE: api/v1/Cats/5
[HttpDelete("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> DeleteCat(long id)
{
var cat = await _context.Cats.FindAsync(id);

@ -14,8 +14,9 @@ using Newtonsoft.Json;
namespace cat_cafe.Controllers
{
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("2.0")]
public class CustomersController : ControllerBase
{
private readonly CatCafeContext _context;
@ -29,8 +30,9 @@ namespace cat_cafe.Controllers
_logger = logger;
}
// GET: api/Customers
// GET: api/v1/Customers
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<CustomerDto>>> GetCustomers()
{
Log.Information(this.Request.Method + " => get All customers");
@ -44,8 +46,9 @@ namespace cat_cafe.Controllers
return Ok(_mapper.Map<List<CustomerDto>>(customers));
}
// GET: api/Customers/5
// GET: api/v1/Customers/5
[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public async Task<ActionResult<CustomerDto>> GetCustomer(long id)
{
Log.Information(this.Request.Method + " => get by ID {@id}",id);
@ -64,9 +67,10 @@ namespace cat_cafe.Controllers
return Ok(_mapper.Map<CustomerDto>(customer));
}
// PUT: api/Customers/5
// PUT: api/v1/Customers/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> PutCustomer(long id, CustomerDto customerDto)
{
Log.Information(this.Request.Method + " => put by ID {@id}", id);
@ -104,9 +108,10 @@ namespace cat_cafe.Controllers
return Ok();
}
// POST: api/Customers
// POST: api/v1/Customers
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
[MapToApiVersion("1.0")]
public async Task<ActionResult<Customer>> PostCustomer(CustomerDto customerDto)
{
Log.Information(this.Request.Method + " => post customer");
@ -123,8 +128,9 @@ namespace cat_cafe.Controllers
return CreatedAtAction("GetCustomer", new { id = customer.Id }, _mapper.Map<Customer>( customer));
}
// DELETE: api/Customers/5
// DELETE: api/v1/Customers/5
[HttpDelete("{id}")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> DeleteCustomer(long id)
{
Log.Information(this.Request.Method + " => delete by ID {@id}", id);

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using cat_cafe.Repositories;
using Serilog;
using Serilog.Sinks.File;
using System.Net.WebSockets;
using cat_cafe.WeSo;
@ -21,6 +20,14 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddControllersWithViews();
builder.Services.AddApiVersioning(o => { o.ReportApiVersions = true; });
builder.Services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
}
);
var app = builder.Build();

@ -7,6 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.12" />

@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "4448c1b2-03b7-4b34-9d5b-ee364ff17986",
"_postman_id": "2347c985-6c2f-4532-abd1-63083abc4efb",
"name": "Cat Café",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "25802734"
@ -94,7 +94,7 @@
"response": []
},
{
"name": "Read all cats",
"name": "Read all cats (v2)",
"request": {
"method": "GET",
"header": [],
@ -165,7 +165,7 @@
"response": []
},
{
"name": "Read all cats",
"name": "Read all cats (v2)",
"request": {
"method": "GET",
"header": [],
@ -206,12 +206,12 @@
"response": []
},
{
"name": "Read all cats",
"name": "Read all cats (v1)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://localhost:5003/gateway/cats",
"raw": "https://localhost:5003/gateway/v1/cats",
"protocol": "https",
"host": [
"localhost"
@ -219,6 +219,7 @@
"port": "5003",
"path": [
"gateway",
"v1",
"cats"
]
}

Loading…
Cancel
Save