Skip to content

Commit 148f545

Browse files
authored
Merge pull request #211 from nanotaboada/feature/dependabot-groups
feature/dependabot-groups
2 parents ea5c68c + de96a21 commit 148f545

File tree

12 files changed

+169
-46
lines changed

12 files changed

+169
-46
lines changed

.codacy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ exclude_paths:
2121
- '**/*Program.cs' # Main entry point, often not useful for static analysis
2222

2323
# Ignore specific folders across any depth in the project
24+
- '**/Configurations/**' # Swagger options, etc
2425
- '**/Data/**' # Repositories, DbContext, database file, etc.
2526
- '**/Enums/**' # Enumeration types
2627
- '**/Mappings/**' # AutoMapper profiles

.github/dependabot.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@
33
version: 2
44
updates:
55
- package-ecosystem: "nuget"
6-
directory: "/"
6+
directory: "/src/Dotnet.Samples.AspNetCore.WebApi"
77
schedule:
88
interval: "daily"
99
groups:
1010
efcore:
1111
patterns:
12-
- "Microsoft.EntityFrameworkCore*"
13-
xunit:
14-
patterns:
15-
- "xunit*"
16-
coverlet:
12+
- "Microsoft.EntityFrameworkCore*"
13+
serilog:
1714
patterns:
18-
- "coverlet*"
15+
- "Serilog*"
16+
17+
- package-ecosystem: "nuget"
18+
directory: "/test/Dotnet.Samples.AspNetCore.WebApi.Tests"
19+
schedule:
20+
interval: "daily"
21+
1922
- package-ecosystem: "github-actions"
2023
directory: "/"
2124
schedule:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 🧪 Web API made with .NET 8 (LTS) and ASP.NET Core 8.0
1+
# 🧪 Web API made with .NET 8 (LTS) and ASP.NET Core
22

33
## Status
44

@@ -16,7 +16,7 @@
1616
1717
## About
1818

19-
Proof of Concept for a Web API made with [ASP.NET Core 8.0](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0) targeting [.NET 8](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8) inspired by the official guide: [Create a web API with ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-8.0&tabs=visual-studio-code).
19+
Proof of Concept for a Web API made with [.NET 8](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8) (LTS) and [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0).
2020

2121
## Start
2222

@@ -30,7 +30,7 @@ dotnet watch run --project src/Dotnet.Samples.AspNetCore.WebApi/Dotnet.Samples.A
3030
https://localhost:9000/swagger/index.html
3131
```
3232

33-
![API Documentation](/docs/Swagger.png)
33+
![API Documentation](/assets/images/Swagger.png)
3434

3535
## Credits
3636

assets/images/Swagger.png

400 KB
Loading

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ignore:
5555
- '**/LICENSE'
5656
- '**/README.md'
5757

58+
- .*\/Configurations\/.*
5859
- .*\/Data\/.*
5960
- .*\/Enums\/.*
6061
- .*\/Mappings\/.*

docs/Swagger.png

-367 KB
Binary file not shown.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Linq;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.OpenApi.Models;
4+
using Swashbuckle.AspNetCore.SwaggerGen;
5+
6+
namespace Dotnet.Samples.AspNetCore.WebApi.Configurations
7+
{
8+
/// <summary>
9+
/// Adds the Bearer security requirement only to operations that have [Authorize] attributes.
10+
/// </summary>
11+
public class AuthorizeCheckOperationFilter : IOperationFilter
12+
{
13+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
14+
{
15+
// Check if [Authorize] is applied at the method or class level
16+
var hasAuthorize =
17+
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
18+
|| context
19+
.MethodInfo.DeclaringType?.GetCustomAttributes(true)
20+
.OfType<AuthorizeAttribute>()
21+
.Any() == true;
22+
23+
// If there's no [Authorize] attribute, skip adding the security requirement
24+
if (!hasAuthorize)
25+
return;
26+
27+
// Add security requirement (shows the lock icon)
28+
operation.Security ??= new List<OpenApiSecurityRequirement>();
29+
operation.Security.Add(
30+
new OpenApiSecurityRequirement
31+
{
32+
{
33+
new OpenApiSecurityScheme
34+
{
35+
Reference = new OpenApiReference
36+
{
37+
Id = "Bearer",
38+
Type = ReferenceType.SecurityScheme
39+
}
40+
},
41+
Array.Empty<string>()
42+
}
43+
}
44+
);
45+
}
46+
}
47+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Reflection;
2+
using Microsoft.OpenApi.Models;
3+
4+
namespace Dotnet.Samples.AspNetCore.WebApi.Configurations;
5+
6+
/// <summary>
7+
/// Provides centralized configuration methods for Swagger/OpenAPI docs.
8+
/// Includes XML comments path resolution and security (JWT Bearer) setup.
9+
/// </summary>
10+
public static class SwaggerGenDefaults
11+
{
12+
/// <summary>
13+
/// Resolves the path to the XML comments file generated from code
14+
/// documentation.
15+
/// This is used to enrich the Swagger UI with method summaries and remarks.
16+
/// </summary>
17+
/// <returns>Full file path to the XML documentation file.</returns>
18+
public static string ConfigureXmlCommentsFilePath()
19+
{
20+
return Path.Combine(
21+
AppContext.BaseDirectory,
22+
$"{Assembly.GetExecutingAssembly().GetName().Name}.xml"
23+
);
24+
}
25+
26+
/// <summary>
27+
/// Configures the OpenAPI security definition for JWT Bearer authentication.
28+
/// This will show the padlock icon in Swagger UI and allow users to
29+
/// authenticate.
30+
/// </summary>
31+
/// <returns>An <see cref="OpenApiSecurityScheme"/> describing the Bearer token format.</returns>
32+
public static OpenApiSecurityScheme ConfigureSecurityDefinition()
33+
{
34+
return new OpenApiSecurityScheme
35+
{
36+
Name = "Authorization",
37+
Type = SecuritySchemeType.Http,
38+
Scheme = "bearer",
39+
BearerFormat = "JWT",
40+
In = ParameterLocation.Header,
41+
Description = "Enter your JWT token below. Example: Bearer {token}"
42+
};
43+
}
44+
45+
/// <summary>
46+
/// Adds a global security requirement to the Swagger spec that applies
47+
/// the Bearer definition.
48+
/// Routes decorated with [Authorize] will be marked as protected in the UI.
49+
/// </summary>
50+
/// <returns>An <see cref="OpenApiSecurityRequirement"/> referencing the Bearer definition.</returns>
51+
public static OpenApiSecurityRequirement ConfigureSecurityRequirement()
52+
{
53+
return new OpenApiSecurityRequirement
54+
{
55+
{
56+
new OpenApiSecurityScheme
57+
{
58+
Reference = new OpenApiReference
59+
{
60+
Id = "Bearer",
61+
Type = ReferenceType.SecurityScheme
62+
}
63+
},
64+
Array.Empty<string>()
65+
}
66+
};
67+
}
68+
}

src/Dotnet.Samples.AspNetCore.WebApi/Controllers/PlayerController.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Dotnet.Samples.AspNetCore.WebApi.Controllers;
99

1010
[ApiController]
11-
[Route("[controller]")]
11+
[Route("players")]
1212
[Produces("application/json")]
1313
public class PlayerController(
1414
IPlayerService playerService,
@@ -94,13 +94,12 @@ public async Task<IResult> GetAsync()
9494
}
9595

9696
/// <summary>
97-
/// Retrieves a Player by its ID
97+
/// Retrieves a Player by its internal Id (GUID)
9898
/// </summary>
99-
/// <param name="id">The ID of the Player</param>
99+
/// <param name="id">The internal Id (GUID) of the Player</param>
100100
/// <response code="200">OK</response>
101101
/// <response code="404">Not Found</response>
102-
[Authorize(Roles = "Admin")]
103-
[ApiExplorerSettings(IgnoreApi = true)]
102+
[Authorize]
104103
[HttpGet("{id:Guid}", Name = "RetrieveById")]
105104
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status200OK)]
106105
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -125,7 +124,7 @@ public async Task<IResult> GetByIdAsync([FromRoute] Guid id)
125124
/// <param name="squadNumber">The Squad Number of the Player</param>
126125
/// <response code="200">OK</response>
127126
/// <response code="404">Not Found</response>
128-
[HttpGet("squadNumber/{squadNumber:int}", Name = "RetrieveBySquadNumber")]
127+
[HttpGet("{squadNumber:int}", Name = "RetrieveBySquadNumber")]
129128
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status200OK)]
130129
[ProducesResponseType(StatusCodes.Status404NotFound)]
131130
public async Task<IResult> GetBySquadNumberAsync([FromRoute] int squadNumber)
@@ -134,15 +133,15 @@ public async Task<IResult> GetBySquadNumberAsync([FromRoute] int squadNumber)
134133
if (player != null)
135134
{
136135
logger.LogInformation(
137-
"GET /players/squad/{SquadNumber} retrieved: {@Player}",
136+
"GET /players/{SquadNumber} retrieved: {@Player}",
138137
squadNumber,
139138
player
140139
);
141140
return TypedResults.Ok(player);
142141
}
143142
else
144143
{
145-
logger.LogWarning("GET /players/squad/{SquadNumber} not found", squadNumber);
144+
logger.LogWarning("GET /players/{SquadNumber} not found", squadNumber);
146145
return TypedResults.NotFound();
147146
}
148147
}

src/Dotnet.Samples.AspNetCore.WebApi/Program.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Reflection;
1+
using Dotnet.Samples.AspNetCore.WebApi.Configurations;
22
using Dotnet.Samples.AspNetCore.WebApi.Data;
33
using Dotnet.Samples.AspNetCore.WebApi.Mappings;
44
using Dotnet.Samples.AspNetCore.WebApi.Models;
@@ -23,13 +23,16 @@
2323
* Logging
2424
* -------------------------------------------------------------------------- */
2525
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger();
26+
27+
/* Serilog ------------------------------------------------------------------ */
2628
builder.Host.UseSerilog();
2729

2830
/* -----------------------------------------------------------------------------
2931
* Services
3032
* -------------------------------------------------------------------------- */
3133
builder.Services.AddControllers();
3234

35+
/* Entity Framework Core ---------------------------------------------------- */
3336
builder.Services.AddDbContextPool<PlayerDbContext>(options =>
3437
{
3538
var dataSource = Path.Combine(AppContext.BaseDirectory, "Data", "players-sqlite3.db");
@@ -44,20 +47,23 @@
4447
builder.Services.AddScoped<IPlayerRepository, PlayerRepository>();
4548
builder.Services.AddScoped<IPlayerService, PlayerService>();
4649
builder.Services.AddMemoryCache();
50+
51+
/* AutoMapper --------------------------------------------------------------- */
4752
builder.Services.AddAutoMapper(typeof(PlayerMappingProfile));
53+
54+
/* FluentValidation --------------------------------------------------------- */
4855
builder.Services.AddScoped<IValidator<PlayerRequestModel>, PlayerRequestModelValidator>();
4956

5057
if (builder.Environment.IsDevelopment())
5158
{
59+
/* Swagger UI ----------------------------------------------------------- */
60+
// https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle
5261
builder.Services.AddSwaggerGen(options =>
5362
{
5463
options.SwaggerDoc("v1", builder.Configuration.GetSection("SwaggerDoc").Get<OpenApiInfo>());
55-
options.IncludeXmlComments(
56-
Path.Combine(
57-
AppContext.BaseDirectory,
58-
$"{Assembly.GetExecutingAssembly().GetName().Name}.xml"
59-
)
60-
);
64+
options.IncludeXmlComments(SwaggerGenDefaults.ConfigureXmlCommentsFilePath());
65+
options.AddSecurityDefinition("Bearer", SwaggerGenDefaults.ConfigureSecurityDefinition());
66+
options.OperationFilter<AuthorizeCheckOperationFilter>();
6167
});
6268
}
6369

@@ -67,16 +73,14 @@
6773
* Middlewares
6874
* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware
6975
* -------------------------------------------------------------------------- */
76+
app.UseSerilogRequestLogging();
77+
7078
if (app.Environment.IsDevelopment())
7179
{
72-
// https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle
7380
app.UseSwagger();
7481
app.UseSwaggerUI();
7582
}
7683

77-
// https://github.com/serilog/serilog-aspnetcore
78-
app.UseSerilogRequestLogging();
79-
8084
// https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl
8185
app.UseHttpsRedirection();
8286

0 commit comments

Comments
 (0)