feat(api): Refactor Program.cs for improved service configuration and logging
- Reorganized the Program.cs file to streamline service configuration, including Serilog setup, API versioning, and health checks. - Added logging configuration to set a minimum logging level for tests, reducing output noise. - Enhanced Swagger integration with detailed API documentation and OAuth2 security definitions. - Implemented ProblemDetails middleware for better error handling and added support for health check endpoints.
This commit is contained in:
@@ -5,240 +5,217 @@ using IamService.API.Application.Behaviors;
|
||||
using IamService.Infrastructure;
|
||||
using Serilog;
|
||||
|
||||
// EN: Configure Serilog early / VI: Cấu hình Serilog sớm
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
.CreateBootstrapLogger();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
try
|
||||
// EN: Configure Serilog with fresh logger for each host (compatible with WebApplicationFactory)
|
||||
// VI: Cấu hình Serilog với logger mới cho mỗi host (tương thích với WebApplicationFactory)
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||||
.ReadFrom.Configuration(context.Configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console());
|
||||
|
||||
// EN: Add Infrastructure services (Identity, OpenIddict, Repositories)
|
||||
// VI: Thêm Infrastructure services (Identity, OpenIddict, Repositories)
|
||||
builder.Services.AddInfrastructure(builder.Configuration, builder.Environment.EnvironmentName);
|
||||
|
||||
// EN: Add MediatR with behaviors / VI: Thêm MediatR với behaviors
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
{
|
||||
Log.Information("Starting IAM Service API / Khởi động IAM Service API");
|
||||
cfg.RegisterServicesFromAssemblyContaining<Program>();
|
||||
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
|
||||
cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>));
|
||||
cfg.AddOpenBehavior(typeof(TransactionBehavior<,>));
|
||||
});
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
// EN: Add FluentValidation / VI: Thêm FluentValidation
|
||||
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
||||
|
||||
// EN: Configure Serilog / VI: Cấu hình Serilog
|
||||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||||
.ReadFrom.Configuration(context.Configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console());
|
||||
// EN: Add API versioning / VI: Thêm API versioning
|
||||
builder.Services.AddApiVersioning(options =>
|
||||
{
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
options.AssumeDefaultVersionWhenUnspecified = true;
|
||||
options.ReportApiVersions = true;
|
||||
options.ApiVersionReader = ApiVersionReader.Combine(
|
||||
new UrlSegmentApiVersionReader(),
|
||||
new HeaderApiVersionReader("X-Api-Version"));
|
||||
})
|
||||
.AddApiExplorer(options =>
|
||||
{
|
||||
options.GroupNameFormat = "'v'VVV";
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
});
|
||||
|
||||
// EN: Add Infrastructure services (Identity, OpenIddict, Repositories)
|
||||
// VI: Thêm Infrastructure services (Identity, OpenIddict, Repositories)
|
||||
builder.Services.AddInfrastructure(builder.Configuration, builder.Environment.EnvironmentName);
|
||||
// EN: Add controllers / VI: Thêm controllers
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// EN: Add MediatR with behaviors / VI: Thêm MediatR với behaviors
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
// EN: Add ProblemDetails middleware (RFC 7807) / VI: Thêm ProblemDetails middleware
|
||||
builder.Services.AddProblemDetails(options =>
|
||||
{
|
||||
options.IncludeExceptionDetails = (ctx, ex) =>
|
||||
builder.Environment.IsDevelopment();
|
||||
});
|
||||
|
||||
// EN: Add Swagger / VI: Thêm Swagger
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new()
|
||||
{
|
||||
cfg.RegisterServicesFromAssemblyContaining<Program>();
|
||||
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
|
||||
cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>));
|
||||
cfg.AddOpenBehavior(typeof(TransactionBehavior<,>));
|
||||
});
|
||||
|
||||
// EN: Add FluentValidation / VI: Thêm FluentValidation
|
||||
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
||||
|
||||
// EN: Add API versioning / VI: Thêm API versioning
|
||||
builder.Services.AddApiVersioning(options =>
|
||||
{
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
options.AssumeDefaultVersionWhenUnspecified = true;
|
||||
options.ReportApiVersions = true;
|
||||
options.ApiVersionReader = ApiVersionReader.Combine(
|
||||
new UrlSegmentApiVersionReader(),
|
||||
new HeaderApiVersionReader("X-Api-Version"));
|
||||
})
|
||||
.AddApiExplorer(options =>
|
||||
{
|
||||
options.GroupNameFormat = "'v'VVV";
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
});
|
||||
|
||||
// EN: Add controllers / VI: Thêm controllers
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// EN: Add ProblemDetails middleware (RFC 7807) / VI: Thêm ProblemDetails middleware
|
||||
builder.Services.AddProblemDetails(options =>
|
||||
{
|
||||
options.IncludeExceptionDetails = (ctx, ex) =>
|
||||
builder.Environment.IsDevelopment();
|
||||
});
|
||||
|
||||
// EN: Add Swagger / VI: Thêm Swagger
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new()
|
||||
Title = "IAM Service API",
|
||||
Version = "v1",
|
||||
Description = """
|
||||
Identity and Access Management Service - OAuth2/OIDC API
|
||||
|
||||
## Authentication
|
||||
This API uses OAuth2 with Password Grant and JWT Bearer tokens.
|
||||
|
||||
## Endpoints
|
||||
- **/api/v1/auth/register** - Register a new user
|
||||
- **/connect/token** - OAuth2 token endpoint
|
||||
- **/api/v1/users** - User management (requires authentication)
|
||||
""",
|
||||
Contact = new()
|
||||
{
|
||||
Title = "IAM Service API",
|
||||
Version = "v1",
|
||||
Description = """
|
||||
Identity and Access Management Service - OAuth2/OIDC API
|
||||
|
||||
## Authentication
|
||||
This API uses OAuth2 with Password Grant and JWT Bearer tokens.
|
||||
|
||||
## Endpoints
|
||||
- **/api/v1/auth/register** - Register a new user
|
||||
- **/connect/token** - OAuth2 token endpoint
|
||||
- **/api/v1/users** - User management (requires authentication)
|
||||
""",
|
||||
Contact = new()
|
||||
{
|
||||
Name = "GoodGo Team",
|
||||
Email = "support@goodgo.com",
|
||||
Url = new Uri("https://github.com/goodgo")
|
||||
},
|
||||
License = new()
|
||||
{
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://opensource.org/licenses/MIT")
|
||||
}
|
||||
});
|
||||
|
||||
// EN: Include XML comments for better documentation
|
||||
// VI: Include XML comments để documentation tốt hơn
|
||||
var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
|
||||
if (File.Exists(xmlPath))
|
||||
Name = "GoodGo Team",
|
||||
Email = "support@goodgo.com",
|
||||
Url = new Uri("https://github.com/goodgo")
|
||||
},
|
||||
License = new()
|
||||
{
|
||||
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://opensource.org/licenses/MIT")
|
||||
}
|
||||
|
||||
// EN: Add OAuth2 security definition / VI: Thêm OAuth2 security definition
|
||||
options.AddSecurityDefinition("oauth2", new()
|
||||
{
|
||||
Type = Microsoft.OpenApi.Models.SecuritySchemeType.OAuth2,
|
||||
Description = "OAuth2 Password Grant flow. Use email as username.",
|
||||
Flows = new()
|
||||
{
|
||||
Password = new()
|
||||
{
|
||||
TokenUrl = new Uri("/connect/token", UriKind.Relative),
|
||||
Scopes = new Dictionary<string, string>
|
||||
{
|
||||
["openid"] = "OpenID - Required for authentication",
|
||||
["profile"] = "Profile - Access to user profile information",
|
||||
["email"] = "Email - Access to user email",
|
||||
["roles"] = "Roles - Access to user roles",
|
||||
["api"] = "API - Full API access"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// EN: Add JWT Bearer security definition / VI: Thêm JWT Bearer security definition
|
||||
options.AddSecurityDefinition("Bearer", new()
|
||||
{
|
||||
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'"
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new()
|
||||
{
|
||||
{
|
||||
new() { Reference = new() { Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, Id = "oauth2" } },
|
||||
["api"]
|
||||
},
|
||||
{
|
||||
new() { Reference = new() { Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, Id = "Bearer" } },
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
|
||||
// EN: Enable annotations / VI: Bật annotations
|
||||
options.EnableAnnotations();
|
||||
});
|
||||
|
||||
// EN: Add health checks / VI: Thêm health checks
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddNpgSql(
|
||||
builder.Configuration.GetConnectionString("DefaultConnection")
|
||||
?? builder.Configuration["DATABASE_URL"]
|
||||
?? "",
|
||||
name: "postgresql",
|
||||
tags: ["db", "postgresql"]);
|
||||
|
||||
// EN: Add CORS / VI: Thêm CORS
|
||||
builder.Services.AddCors(options =>
|
||||
// EN: Include XML comments for better documentation
|
||||
// VI: Include XML comments để documentation tốt hơn
|
||||
var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
|
||||
if (File.Exists(xmlPath))
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// EN: Configure middleware pipeline / VI: Cấu hình middleware pipeline
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseProblemDetails();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "IAM Service API v1");
|
||||
c.RoutePrefix = "swagger";
|
||||
c.OAuthClientId("swagger-ui");
|
||||
c.OAuthUsePkce();
|
||||
});
|
||||
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
|
||||
}
|
||||
|
||||
app.UseCors();
|
||||
app.UseRouting();
|
||||
|
||||
// EN: Debug middleware for /connect/* endpoints
|
||||
// VI: Debug middleware cho /connect/* endpoints
|
||||
app.Use(async (context, next) =>
|
||||
// EN: Add OAuth2 security definition / VI: Thêm OAuth2 security definition
|
||||
options.AddSecurityDefinition("oauth2", new()
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/connect"))
|
||||
Type = Microsoft.OpenApi.Models.SecuritySchemeType.OAuth2,
|
||||
Description = "OAuth2 Password Grant flow. Use email as username.",
|
||||
Flows = new()
|
||||
{
|
||||
Log.Information(">>> [DEBUG] Request to {Path} - Method: {Method}",
|
||||
context.Request.Path, context.Request.Method);
|
||||
}
|
||||
await next();
|
||||
if (context.Request.Path.StartsWithSegments("/connect"))
|
||||
{
|
||||
Log.Information("<<< [DEBUG] Response from {Path} - Status: {StatusCode}",
|
||||
context.Request.Path, context.Response.StatusCode);
|
||||
Password = new()
|
||||
{
|
||||
TokenUrl = new Uri("/connect/token", UriKind.Relative),
|
||||
Scopes = new Dictionary<string, string>
|
||||
{
|
||||
["openid"] = "OpenID - Required for authentication",
|
||||
["profile"] = "Profile - Access to user profile information",
|
||||
["email"] = "Email - Access to user email",
|
||||
["roles"] = "Roles - Access to user roles",
|
||||
["api"] = "API - Full API access"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// EN: Authentication and Authorization / VI: Xác thực và phân quyền
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// EN: Map health check endpoints / VI: Map health check endpoints
|
||||
app.MapHealthChecks("/health");
|
||||
app.MapHealthChecks("/health/live", new()
|
||||
// EN: Add JWT Bearer security definition / VI: Thêm JWT Bearer security definition
|
||||
options.AddSecurityDefinition("Bearer", new()
|
||||
{
|
||||
Predicate = _ => false // EN: Just checks app is running / VI: Chỉ kiểm tra app đang chạy
|
||||
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'"
|
||||
});
|
||||
app.MapHealthChecks("/health/ready");
|
||||
|
||||
// EN: Map controllers / VI: Map controllers
|
||||
app.MapControllers();
|
||||
options.AddSecurityRequirement(new()
|
||||
{
|
||||
{
|
||||
new() { Reference = new() { Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, Id = "oauth2" } },
|
||||
["api"]
|
||||
},
|
||||
{
|
||||
new() { Reference = new() { Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, Id = "Bearer" } },
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
|
||||
// EN: Run the application / VI: Chạy ứng dụng
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
// EN: Enable annotations / VI: Bật annotations
|
||||
options.EnableAnnotations();
|
||||
});
|
||||
|
||||
// EN: Add health checks / VI: Thêm health checks
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||
?? builder.Configuration["DATABASE_URL"]
|
||||
?? "";
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
Log.Fatal(ex, "Application terminated unexpectedly / Ứng dụng kết thúc bất ngờ");
|
||||
throw;
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddNpgSql(connectionString, name: "postgresql", tags: ["db", "postgresql"]);
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
builder.Services.AddHealthChecks();
|
||||
}
|
||||
|
||||
// EN: Add CORS / VI: Thêm CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// EN: Log startup message using ILogger
|
||||
// VI: Log thông báo khởi động sử dụng ILogger
|
||||
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogInformation("Starting IAM Service API / Khởi động IAM Service API");
|
||||
|
||||
// EN: Configure middleware pipeline / VI: Cấu hình middleware pipeline
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseProblemDetails();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "IAM Service API v1");
|
||||
c.RoutePrefix = "swagger";
|
||||
c.OAuthClientId("swagger-ui");
|
||||
c.OAuthUsePkce();
|
||||
});
|
||||
}
|
||||
|
||||
app.UseCors();
|
||||
app.UseRouting();
|
||||
|
||||
// EN: Authentication and Authorization / VI: Xác thực và phân quyền
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// EN: Map health check endpoints / VI: Map health check endpoints
|
||||
app.MapHealthChecks("/health");
|
||||
app.MapHealthChecks("/health/live", new()
|
||||
{
|
||||
Predicate = _ => false // EN: Just checks app is running / VI: Chỉ kiểm tra app đang chạy
|
||||
});
|
||||
app.MapHealthChecks("/health/ready");
|
||||
|
||||
// EN: Map controllers / VI: Map controllers
|
||||
app.MapControllers();
|
||||
|
||||
// EN: Run the application / VI: Chạy ứng dụng
|
||||
app.Run();
|
||||
|
||||
// EN: Make Program class accessible for integration tests
|
||||
// VI: Làm cho class Program có thể truy cập cho integration tests
|
||||
public partial class Program { }
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IamService.Infrastructure;
|
||||
using IamService.Infrastructure.Caching;
|
||||
using StackExchange.Redis;
|
||||
@@ -66,6 +67,13 @@ public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
options.UseOpenIddict(); // EN: Required for OpenIddict / VI: Cần cho OpenIddict
|
||||
options.EnableSensitiveDataLogging();
|
||||
});
|
||||
|
||||
// EN: Set minimum logging level to reduce test output noise
|
||||
// VI: Đặt mức logging tối thiểu để giảm nhiễu output test
|
||||
services.AddLogging(logging =>
|
||||
{
|
||||
logging.SetMinimumLevel(LogLevel.Warning);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user