Skip to content

Commit 5f4603a

Browse files
committed
Added versioning and more notes
1 parent 1acc4ec commit 5f4603a

File tree

10 files changed

+260
-4
lines changed

10 files changed

+260
-4
lines changed

sessions/Season-05/0505-ApiDesignAndDevelopment/README.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ In response to [question #79](https://github.com/csharpfritz/csharp_with_csharpf
55
## Topics
66

77
- Build an API with ASP()[].NET Core and Swagger
8-
- Design patterns for APIs
98
- API Versioning
109
- Caching
1110
- Pagination Strategies
@@ -30,14 +29,48 @@ Customize it further with [Swashbuckle tools](https://docs.microsoft.com/aspnet/
3029

3130
You can generate and build client-side code with the dotnet-openapi tool. Details at https://docs.microsoft.com/aspnet/core/web-api/Microsoft.dotnet-openapi
3231

33-
## Design Patterns
34-
3532
## API Versioning
3633

34+
Versioning in Swagger, [Scott Hanselman has a blog post](https://www.hanselman.com/blog/aspnet-core-restful-web-api-versioning-made-easy) to get us started.
35+
3736
## Caching
3837

3938
## Pagination
4039

40+
Pagination has no ideal solution, with different approaches possible:
41+
42+
- Cursor-based paging
43+
- Offset/Count paging
44+
- Keyset-based paging
45+
46+
### Cursor-based paging
47+
48+
```
49+
/products?cursor=abcdefg&limit=10
50+
```
51+
52+
Returns data and an indicator like `next_cursor` so you can make your next request:
53+
54+
```
55+
/products?cursor=hijkl&limit=10
56+
```
57+
58+
### Offset/Count paging
59+
60+
```
61+
/products?offset=10&count=10
62+
```
63+
64+
Returns 10 records starting with record #11
65+
66+
### Keyset-based paging
67+
68+
```
69+
/orders?limit=10&since_id=10123
70+
```
71+
72+
Returns 10 records created AFTER `OrderId` with key 10123
73+
4174
## Uploading files to an API
4275

4376
Instead of receiving an input parameter of type string or int, receive an input parameter of type `IFormFile` like this:
@@ -91,7 +124,7 @@ Blog at: https://devblogs.microsoft.com/aspnet/jwt-validation-and-authorization-
91124

92125
## Configuring CORS
93126

94-
Cross-Origin Resource Sharing (CORS) can be configured in your application with a policy using settings like the following:
127+
[Cross-Origin Resource Sharing (CORS) can be configured](https://docs.microsoft.com/aspnet/core/security/cors?view=aspnetcore-5.0) in your application with a policy using settings like the following:
95128

96129
```csharp
97130

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace VersionApi.Controllers
9+
{
10+
11+
[ApiVersion( "1.0" )]
12+
[ApiVersion( "2.0" )]
13+
[ApiController]
14+
[Route( "api/v{version:apiVersion}/[controller]" )]
15+
public class WeatherForecastController : ControllerBase
16+
{
17+
private static readonly string[] Summaries = new[]
18+
{
19+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
20+
};
21+
22+
private readonly ILogger<WeatherForecastController> _logger;
23+
24+
public WeatherForecastController(ILogger<WeatherForecastController> logger)
25+
{
26+
_logger = logger;
27+
}
28+
29+
[HttpGet]
30+
public IEnumerable<WeatherForecast> Get()
31+
{
32+
var rng = new Random();
33+
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
34+
{
35+
Date = DateTime.Now.AddDays(index),
36+
TemperatureC = rng.Next(-20, 55),
37+
Summary = Summaries[rng.Next(Summaries.Length)]
38+
})
39+
.ToArray();
40+
}
41+
}
42+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace VersionApi
11+
{
12+
public class Program
13+
{
14+
public static void Main(string[] args)
15+
{
16+
CreateHostBuilder(args).Build().Run();
17+
}
18+
19+
public static IHostBuilder CreateHostBuilder(string[] args) =>
20+
Host.CreateDefaultBuilder(args)
21+
.ConfigureWebHostDefaults(webBuilder =>
22+
{
23+
webBuilder.UseStartup<Startup>();
24+
});
25+
}
26+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:34594",
8+
"sslPort": 44380
9+
}
10+
},
11+
"profiles": {
12+
"VersionApi": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"launchUrl": "swagger",
17+
"applicationUrl": "https://localhost:5001;http://localhost:5000",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
},
22+
"IIS Express": {
23+
"commandName": "IISExpress",
24+
"launchBrowser": true,
25+
"launchUrl": "swagger",
26+
"environmentVariables": {
27+
"ASPNETCORE_ENVIRONMENT": "Development"
28+
}
29+
}
30+
}
31+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.HttpsPolicy;
8+
using Microsoft.AspNetCore.Mvc;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Hosting;
12+
using Microsoft.Extensions.Logging;
13+
using Microsoft.OpenApi.Models;
14+
15+
namespace VersionApi
16+
{
17+
public class Startup
18+
{
19+
public Startup(IConfiguration configuration)
20+
{
21+
Configuration = configuration;
22+
}
23+
24+
public IConfiguration Configuration { get; }
25+
26+
// This method gets called by the runtime. Use this method to add services to the container.
27+
public void ConfigureServices(IServiceCollection services)
28+
{
29+
30+
services.AddControllers();
31+
services.AddApiVersioning(o => {
32+
o.AssumeDefaultVersionWhenUnspecified = true;
33+
o.DefaultApiVersion = new ApiVersion(1, 0);
34+
});
35+
services.AddSwaggerGen(c =>
36+
{
37+
c.SwaggerDoc("v1", new OpenApiInfo { Title = "VersionApi", Version = "v1" });
38+
c.SwaggerDoc("v2", new OpenApiInfo { Title = "VersionApi", Version = "v2" });
39+
});
40+
}
41+
42+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
43+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
44+
{
45+
if (env.IsDevelopment())
46+
{
47+
app.UseDeveloperExceptionPage();
48+
app.UseSwagger();
49+
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "VersionApi v1"));
50+
}
51+
52+
app.UseHttpsRedirection();
53+
54+
app.UseRouting();
55+
56+
app.UseAuthorization();
57+
58+
app.UseEndpoints(endpoints =>
59+
{
60+
endpoints.MapControllers();
61+
});
62+
}
63+
}
64+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
9+
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace VersionApi
4+
{
5+
public class WeatherForecast
6+
{
7+
public DateTime Date { get; set; }
8+
9+
public int TemperatureC { get; set; }
10+
11+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
12+
13+
public string Summary { get; set; }
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft": "Warning",
6+
"Microsoft.Hosting.Lifetime": "Information"
7+
}
8+
}
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft": "Warning",
6+
"Microsoft.Hosting.Lifetime": "Information"
7+
}
8+
},
9+
"AllowedHosts": "*"
10+
}

sessions/Season-05/0505-ApiDesignAndDevelopment/src/src.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstApi", "FirstApi\FirstA
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorClient", "BlazorClient\BlazorClient.csproj", "{778E6CA5-0948-41BD-8EC6-9EBC9B27B5B8}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionApi", "VersionApi\VersionApi.csproj", "{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}"
11+
EndProject
1012
Global
1113
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1214
Debug|Any CPU = Debug|Any CPU
@@ -44,5 +46,17 @@ Global
4446
{778E6CA5-0948-41BD-8EC6-9EBC9B27B5B8}.Release|x64.Build.0 = Release|Any CPU
4547
{778E6CA5-0948-41BD-8EC6-9EBC9B27B5B8}.Release|x86.ActiveCfg = Release|Any CPU
4648
{778E6CA5-0948-41BD-8EC6-9EBC9B27B5B8}.Release|x86.Build.0 = Release|Any CPU
49+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
51+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|x64.ActiveCfg = Debug|Any CPU
52+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|x64.Build.0 = Debug|Any CPU
53+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|x86.ActiveCfg = Debug|Any CPU
54+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Debug|x86.Build.0 = Debug|Any CPU
55+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
56+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|Any CPU.Build.0 = Release|Any CPU
57+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|x64.ActiveCfg = Release|Any CPU
58+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|x64.Build.0 = Release|Any CPU
59+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|x86.ActiveCfg = Release|Any CPU
60+
{B06783DB-B55C-4D91-8ACD-BCAF42E562A1}.Release|x86.Build.0 = Release|Any CPU
4761
EndGlobalSection
4862
EndGlobal

0 commit comments

Comments
 (0)