diff --git a/TestHosts/TestHosts/Program.cs b/TestHosts/TestHosts/Program.cs index 8c5e40b..8d5e154 100644 --- a/TestHosts/TestHosts/Program.cs +++ b/TestHosts/TestHosts/Program.cs @@ -1,60 +1,195 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Shared.EntityFramework; - -namespace TestHosts -{ - using System.IO; - using Database.PataPawa; + + using CoreWCF; + using CoreWCF.Configuration; + using CoreWCF.Description; + using HealthChecks.UI.Client; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Diagnostics.HealthChecks; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Diagnostics.HealthChecks; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Hosting.WindowsServices; + using Microsoft.Extensions.Logging; + using NLog; + using NLog.Web; + using Shared.EntityFramework; + using Shared.Extensions; + using Shared.General; + using Shared.Middleware; + using System; + using System.IO; + using Microsoft.EntityFrameworkCore; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + using Shared.Logger.TennantContext; + using TestHosts; + using TestHosts.Common; + using TestHosts.Database.PataPawa; + using TestHosts.Database.TestBank; + using TestHosts.SoapServices; + using LogLevel = Microsoft.Extensions.Logging.LogLevel; - public class Program - { - public static void Main(string[] args) - { - Program.CreateHostBuilder(args).Build().Run(); + try { + + WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = args, ContentRootPath = AppContext.BaseDirectory }); + + // ---------------------------------------------------------------------- + // Load custom hosting configuration (your existing hosting.json setup) + // ---------------------------------------------------------------------- + FileInfo fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); + builder.Configuration.SetBasePath(fi.Directory!.FullName).AddJsonFile("hosting.json", optional: false, reloadOnChange: true).AddJsonFile("hosting.development.json", optional: true, reloadOnChange: true) + .AddJsonFile("/home/txnproc/config/appsettings.json", true, true) + .AddJsonFile($"/home/txnproc/config/appsettings.{builder.Environment.EnvironmentName}.json", optional: true) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables(); + + ConfigurationReader.Initialise(builder.Configuration); + // ---------------------------------------------------------------------- + // Configure Windows Service mode + // ---------------------------------------------------------------------- + if (WindowsServiceHelpers.IsWindowsService()) + builder.Host.UseWindowsService(); + + // ---------------------------------------------------------------------- + // Configure Kestrel + // ---------------------------------------------------------------------- + builder.WebHost.ConfigureKestrel(options => { options.AllowSynchronousIO = true; }); + +// ---------------------------------------------------------------------- +// Configure NLog +// ---------------------------------------------------------------------- + string nlogConfigFilename = "nlog.config"; + if (builder.Environment.IsDevelopment()) { + String devFile = Path.Combine(builder.Environment.ContentRootPath, "nlog.development.config"); + if (File.Exists(devFile)) + nlogConfigFilename = "nlog.development.config"; } - public static IHostBuilder CreateHostBuilder(string[] args) + LogManager.Setup().LoadConfigurationFromFile(Path.Combine(builder.Environment.ContentRootPath, nlogConfigFilename)); + builder.Logging.ClearProviders(); + builder.Host.UseNLog(); + +// ---------------------------------------------------------------------- +// Add application and framework services +// ---------------------------------------------------------------------- + builder.Services.AddControllers().AddNewtonsoftJson(options => { - FileInfo fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location); - - //At this stage, we only need our hosting file for ip and ports - IConfigurationRoot config = new ConfigurationBuilder().SetBasePath(fi.Directory.FullName) - .AddJsonFile("hosting.json", optional: false) - .AddJsonFile("hosting.development.json", optional: true) - .AddEnvironmentVariables().Build(); - - IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args); - hostBuilder.UseWindowsService(); - hostBuilder.ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - webBuilder.UseConfiguration(config); - webBuilder.UseKestrel(); - webBuilder.ConfigureKestrel((context, - options) => { - options.AllowSynchronousIO = true; - }); - }); - hostBuilder.ConfigureServices(services => - { - services.AddHostedService(provider => - { - IDbContextResolver contextResolver = provider.GetRequiredService>(); - PendingPrePaymentProcessor worker = new (contextResolver); - - return worker; - }); - }); - - return hostBuilder; + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.TypeNameHandling = TypeNameHandling.None; + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }); + builder.Services.AddHealthChecks().AddSqlServer(connectionString: ConfigurationReader.GetConnectionString("HealthCheck"), + healthQuery: "SELECT 1;", + name: "Read Model Server", + failureStatus: HealthStatus.Degraded, + tags: new[] { "db", "sql", "sqlserver" }); ; + builder.Services.AddServiceModelServices().AddServiceModelMetadata(); + + builder.Services.AddSingleton(typeof(IDbContextResolver<>), typeof(DbContextResolver<>)); + if (builder.Environment.IsEnvironment("IntegrationTest") || builder.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase") == true) + { + builder.Services.AddDbContext(builder => builder.UseInMemoryDatabase("TestBankReadModel")); + builder.Services.AddDbContext(builder => builder.UseInMemoryDatabase("PataPawaReadModel")); + } - } + else + { + String testBankConnectionString = ConfigurationReader.GetConnectionString("TestBankReadModel"); + builder.Services.AddDbContext(builder => builder.UseSqlServer(testBankConnectionString)); + + String pataPawaConnectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); + builder.Services.AddDbContext(builder => builder.UseSqlServer(pataPawaConnectionString)); + } + builder.Services.AddScoped(x => new TenantContext()); + builder.Services.AddSingleton(); + // Add your background hosted service + builder.Services.AddHostedService(provider => { + IDbContextResolver contextResolver = provider.GetRequiredService>(); + return new PendingPrePaymentProcessor(contextResolver); + }); + +// Database initialization will now be handled by a hosted service + builder.Services.AddHostedService(); + + builder.Services.AddScoped(x => new TenantContext()); + builder.Services.AddSingleton(); + builder.Services.AddMvc(); + + builder.Services.AddServiceModelServices().AddServiceModelMetadata(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + + bool logRequests = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogRequests", true); + bool logResponses = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogResponses", true); + LogLevel middlewareLogLevel = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "MiddlewareLogLevel", LogLevel.Warning); + + RequestResponseMiddlewareLoggingConfig config = + new RequestResponseMiddlewareLoggingConfig(middlewareLogLevel, logRequests, logResponses); + + builder.Services.AddSingleton(config); + + // ---------------------------------------------------------------------- + // Build the app + // ---------------------------------------------------------------------- + WebApplication app = builder.Build(); + + // Create a scoped logger and assign it + using (var scope = app.Services.CreateScope()) + { + var loggerFactory = scope.ServiceProvider.GetRequiredService(); + var loggerObject = loggerFactory.CreateLogger("AppStartup"); + Shared.Logger.Logger.Initialise(loggerObject); + } + +// ---------------------------------------------------------------------- +// Middleware pipeline +// ---------------------------------------------------------------------- + if (app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + } + +// Custom middleware + app.UseMiddleware(); + app.AddRequestLogging(); + app.AddResponseLogging(); + app.AddExceptionHandler(); + + app.UseRouting(); + +// ---------------------------------------------------------------------- +// Endpoints +// ---------------------------------------------------------------------- + app.MapControllers(); + + app.MapHealthChecks("health", new HealthCheckOptions { Predicate = _ => true, ResponseWriter = Shared.HealthChecks.HealthCheckMiddleware.WriteResponse }); + + app.MapHealthChecks("healthui", new HealthCheckOptions { Predicate = _ => true, ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); + +// ---------------------------------------------------------------------- +// CoreWCF setup +// ---------------------------------------------------------------------- + app.UseServiceModel(builder => { builder.AddService(options => { options.DebugBehavior.IncludeExceptionDetailInFaults = true; }).AddServiceEndpoint(new BasicHttpBinding(), "/PataPawaPostPayService/basichttp"); }); + + ServiceMetadataBehavior metadataBehavior = app.Services.GetRequiredService(); + metadataBehavior.HttpGetEnabled = true; + +// ---------------------------------------------------------------------- +// Start the application +// ---------------------------------------------------------------------- + app.Run(); + + Shared.Logger.Logger.LogWarning("Application started successfully"); } + catch (Exception ex) { + Shared.Logger.Logger.LogError("Application stopped due to exception", ex); + throw; + } + finally { + LogManager.Shutdown(); + } diff --git a/TestHosts/TestHosts/Startup.cs b/TestHosts/TestHosts/Startup.cs index db60d9d..bc55fbe 100644 --- a/TestHosts/TestHosts/Startup.cs +++ b/TestHosts/TestHosts/Startup.cs @@ -3,9 +3,16 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using Shared.Logger.TennantContext; +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using TestHosts.Common; +using TestHosts.Database.PataPawa; +using TestHosts.Database.TestBank; namespace TestHosts { @@ -23,6 +30,7 @@ namespace TestHosts using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; + using NLog; using Shared.EntityFramework; using Shared.Extensions; using Shared.General; @@ -42,174 +50,6 @@ namespace TestHosts using TestHosts.SoapServices; using ILogger = Microsoft.Extensions.Logging.ILogger; - public class Startup - { - public Startup(IWebHostEnvironment webHostEnvironment) - { - IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(webHostEnvironment.ContentRootPath) - .AddJsonFile("/home/txnproc/config/appsettings.json", true, true) - .AddJsonFile($"/home/txnproc/config/appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); - - Startup.Configuration = builder.Build(); - Startup.WebHostEnvironment = webHostEnvironment; - } - - public static IConfigurationRoot Configuration { get; set; } - - public static IWebHostEnvironment WebHostEnvironment { get; set; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - ConfigurationReader.Initialise(Startup.Configuration); - - services.AddHealthChecks().AddSqlServer(connectionString: ConfigurationReader.GetConnectionString("HealthCheck"), - healthQuery: "SELECT 1;", - name: "Read Model Server", - failureStatus: HealthStatus.Degraded, - tags: new[] { "db", "sql", "sqlserver" }); - - services.AddControllers().AddNewtonsoftJson(options => - { - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - options.SerializerSettings.TypeNameHandling = TypeNameHandling.None; - options.SerializerSettings.Formatting = Formatting.Indented; - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); - - // Register the Swagger generator, defining 1 or more Swagger documents - //services.AddSwaggerGen(c => - // { - // c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - // }); - services.AddSingleton(typeof(IDbContextResolver<>), typeof(DbContextResolver<>)); - if (Startup.WebHostEnvironment.IsEnvironment("IntegrationTest") || Startup.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase") == true) - { - services.AddDbContext(builder => builder.UseInMemoryDatabase("TestBankReadModel")); - services.AddDbContext(builder => builder.UseInMemoryDatabase("PataPawaReadModel")); - - } - else - { - String testBankConnectionString = ConfigurationReader.GetConnectionString("TestBankReadModel"); - services.AddDbContext(builder => builder.UseSqlServer(testBankConnectionString)); - - String pataPawaConnectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - services.AddDbContext(builder => builder.UseSqlServer(pataPawaConnectionString)); - } - services.AddScoped(x => new TenantContext()); - services.AddSingleton(); - services.AddMvc(); - - services.AddServiceModelServices().AddServiceModelMetadata(); - services.AddSingleton(); - services.AddSingleton(); - - - bool logRequests = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogRequests", true); - bool logResponses = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogResponses", true); - LogLevel middlewareLogLevel = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "MiddlewareLogLevel", LogLevel.Warning); - - RequestResponseMiddlewareLoggingConfig config = - new RequestResponseMiddlewareLoggingConfig(middlewareLogLevel, logRequests, logResponses); - - services.AddSingleton(config); - } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - String nlogConfigFilename = "nlog.config"; - - if (env.IsDevelopment()) - { - var developmentNlogConfigFilename = "nlog.development.config"; - if (File.Exists(Path.Combine(env.ContentRootPath, developmentNlogConfigFilename))) - { - nlogConfigFilename = developmentNlogConfigFilename; - } - app.UseDeveloperExceptionPage(); - } - - loggerFactory.ConfigureNLog(Path.Combine(env.ContentRootPath, nlogConfigFilename)); - loggerFactory.AddNLog(); - - ILogger logger = loggerFactory.CreateLogger("TestHosts"); - - Logger.Initialise(logger); - app.UseMiddleware(); - app.AddRequestLogging(); - app.AddResponseLogging(); - app.AddExceptionHandler(); - - app.UseRouting(); - - // Enable middleware to serve generated Swagger as a JSON endpoint. - //app.UseSwagger(); - - //// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), - //// specifying the Swagger JSON endpoint. - //app.UseSwaggerUI(c => - // { - // c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - // c.RoutePrefix = string.Empty; - // }); - - // this will do the initial DB population - InitializeDatabase(app).Wait(CancellationToken.None); - app.UseEndpoints(endpoints => { - endpoints.MapControllers(); - endpoints.MapHealthChecks("health", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = Shared.HealthChecks.HealthCheckMiddleware.WriteResponse - }); - endpoints.MapHealthChecks("healthui", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - }); - - app.UseServiceModel(builder => { - builder.AddService((serviceOptions) => { - serviceOptions.DebugBehavior.IncludeExceptionDetailInFaults = true; - }) - // Add a BasicHttpBinding at a specific endpoint - .AddServiceEndpoint(new BasicHttpBinding(), "/PataPawaPostPayService/basichttp"); - }); - - ServiceMetadataBehavior serviceMetadataBehavior = app.ApplicationServices.GetRequiredService(); - serviceMetadataBehavior.HttpGetEnabled = true; - - - - } - - async Task InitializeDatabase(IApplicationBuilder app) - { - using (IServiceScope serviceScope = app.ApplicationServices.GetService().CreateScope()) - { - TestBankContext testbankDbContext = serviceScope.ServiceProvider.GetRequiredService(); - if (testbankDbContext.Database.IsRelational()) - { - testbankDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); - await testbankDbContext.MigrateAsync(CancellationToken.None); - } - - PataPawaContext pataPawaContext = serviceScope.ServiceProvider.GetRequiredService(); - if (pataPawaContext.Database.IsRelational()) - { - pataPawaContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); - pataPawaContext.Database.MigrateAsync(CancellationToken.None); - } - } - } - } - [ExcludeFromCodeCoverage] public class PendingPrePaymentProcessor : BackgroundService{ private readonly IDbContextResolver Resolver; @@ -267,3 +107,41 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken){ } } } + +public sealed class DatabaseInitializerHostedService : IHostedService +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public DatabaseInitializerHostedService(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Starting database initialization..."); + + try + { + using var scope = _serviceProvider.CreateScope(); + var pataPawaContext = scope.ServiceProvider.GetRequiredService(); + + // Example: apply migrations or seed data + await pataPawaContext.Database.MigrateAsync(cancellationToken); + + var bankContext = scope.ServiceProvider.GetRequiredService(); + await bankContext.Database.MigrateAsync(cancellationToken); + + _logger.LogInformation("Database initialization completed successfully."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Database initialization failed."); + throw; // Let the host fail fast if initialization is critical + } + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/TestHosts/TestHosts/TestHosts.csproj b/TestHosts/TestHosts/TestHosts.csproj index fa3e22f..6c8f452 100644 --- a/TestHosts/TestHosts/TestHosts.csproj +++ b/TestHosts/TestHosts/TestHosts.csproj @@ -12,23 +12,24 @@ - - - - - - - - + + + + + + + + - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive