using System.CommandLine; using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using OptixServe.Api.Configuration; using OptixServe.Api.Endpoints; using OptixServe.Api.Services; using OptixServe.Core.Data; using OptixServe.Core.Services; using OptixServe.Api.Utilites; class Program { /// /// Main method that configures and runs the application /// /// Command line arguments /// A Task representing the asynchronous operation static async Task Main(string[] args) { var rootCommand = new RootCommand("OptixServe API"); // Configure the --config/-c command line option var configOption = new Option("--config", "-c") { Description = "Config file path", CustomParser = arg => { if (!arg.Tokens.Any()) return null; var filePath = arg.Tokens.Single().Value; if (!File.Exists(filePath)) { arg.AddError($"Configuration file not found: {filePath}"); return null; } return new FileInfo(filePath); } }; rootCommand.Add(configOption); // Set handle for rootCommand // The handle prototype and way of parsing arguments // is changed in System.CommandLine 2.0.0-beta5 rootCommand.SetAction((ParseResult parseResult) => { var builder = WebApplication.CreateSlimBuilder(args); var configFile = parseResult.GetValue(configOption); builder.AddConfigurationWithCommand(configFile); builder.RegisterServices(); builder.RegiserJsonContext(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); using (var scope = app.Services.CreateScope()) { var initializer = scope.ServiceProvider.GetRequiredService(); initializer.Initialize(); } var apiGroup = app.MapGroup("api/v1"); StartupHelper.RegisterEndpoints(apiGroup); app.Run(); }); // Parse arguments then invoke the command var parseResult = rootCommand.Parse(args); await parseResult.InvokeAsync(); } } /// /// Contains extension methods for WebApplicationBuilder and WebApplication /// static class StartupHelper { /// /// Adds configuration sources to the application builder /// /// WebApplicationBuilder instance /// Optional configuration file specified via command line public static void AddConfigurationWithCommand(this WebApplicationBuilder builder, FileInfo? file) { // Configure configuration sources in specified order var configurationBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) .AddJsonFile("config.json", optional: true) .AddJsonFile($"config.{builder.Environment.EnvironmentName}.json", optional: true); // Add command-line specified config file if provided if (file != null) { configurationBuilder.AddJsonFile(file.FullName, optional: false); } builder.Configuration.AddConfiguration(configurationBuilder.Build()); } /// /// Configures DbContext services /// /// /// /// public static IServiceCollection AddAppDatabase(this IServiceCollection services, DatabaseSettings dbSettings) { services.AddDbContext(options => DatabaseHelper.ConfigureDbContext(options, dbSettings)); return services; } /// /// Configures services for DI /// /// WebApplicationBuilder instance public static void RegisterServices(this WebApplicationBuilder builder) { // Add configuration class var optixSettigns = builder.Configuration.GetSection("OptixServe"); var onConfigSettings = optixSettigns.Get(); builder.Services.Configure(optixSettigns); // Add DBContext class builder.Services.AddAppDatabase(onConfigSettings?.Database!); builder.Services.AddScoped(); // Application services builder.Services.AddScoped(); builder.Services.AddScoped(); // Add Authentication and Authorization builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { var jwtSettings = onConfigSettings?.Jwt ?? throw new ArgumentNullException(nameof(builder), "JWT settings are not configured."); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings.Issuer, ValidAudience = jwtSettings.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)) }; }); builder.Services.AddAuthorization(); } /// /// Configures JSON serialization options with custom context /// /// WebApplicationBuilder instance public static void RegiserJsonContext(this WebApplicationBuilder builder) { builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Add(UserJsonContext.Default); options.SerializerOptions.TypeInfoResolverChain.Add(VersionJsonContext.Default); }); } /// /// Registers all API endpoints /// /// Root RouteGroupBuilder instance public static void RegisterEndpoints(RouteGroupBuilder rootGroup) { UserEndpoint.Register(rootGroup); VersionEndpoint.Register(rootGroup); } }