diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs index 3c432a6d0..75525a2a5 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs @@ -38,3 +38,4 @@ global using System.Threading.Tasks; global using System.Threading; global using System; global using Microsoft.IdentityModel.Tokens; +global using Serilog.Context; \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs index 70506fbda..9fd652704 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs @@ -1,24 +1,215 @@ -await BuildWebHost(args).RunAsync(); +var appName = "Ordering.API"; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureAppConfiguration(cb => { + var sources = cb.Sources; + sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() { + Optional = true, + Path = "appsettings.localhost.json", + ReloadOnChange = false + }); +}); +builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); +builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(builder.Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(new Uri(builder.Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(new Uri(builder.Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) + .AddUrlGroup(new Uri(builder.Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); +builder.Services.AddCustomMvc(builder.Configuration) + .AddCustomAuthentication(builder.Configuration) + //.AddCustomAuthorization(Configuration) + .AddApplicationServices() + .AddGrpcServices(); +var app = builder.Build(); +if (app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); +} +else { + app.UseExceptionHandler("/Home/Error"); +} +var pathBase = builder.Configuration["PATH_BASE"]; +if (!string.IsNullOrEmpty(pathBase)) { + app.UsePathBase(pathBase); +} -IWebHost BuildWebHost(string[] args) => - WebHost - .CreateDefaultBuilder(args) - .ConfigureAppConfiguration(cb => +app.UseHttpsRedirection(); + +app.UseSwagger().UseSwaggerUI(c => +{ + c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Purchase BFF V1"); + + c.OAuthClientId("webshoppingaggswaggerui"); + c.OAuthClientSecret(string.Empty); + c.OAuthRealm(string.Empty); + c.OAuthAppName("web shopping bff Swagger UI"); +}); + +app.UseRouting(); +app.UseCors("CorsPolicy"); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute(); + app.MapControllers(); + app.MapHealthChecks("/hc", new HealthCheckOptions() { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + app.MapHealthChecks("/liveness", new HealthCheckOptions { + Predicate = r => r.Name.Contains("self") + }); + +try { + + Log.Information("Starts Web Application ({ApplicationContext})...", Program.AppName); + await app.RunAsync(); + + return 0; +} +catch (Exception ex) { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); + return 1; +} +finally { + Log.CloseAndFlush(); +} + + +Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", Program.AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + //.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + //.WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) + .ReadFrom.Configuration(configuration) + .CreateLogger(); +} +public partial class Program { + + public static string Namespace = typeof(Program).Assembly.GetName().Name; + public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); +} + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + + var identityUrl = configuration.GetValue("urls:identity"); + services.AddAuthentication("Bearer") + .AddJwtBearer(options => { - var sources = cb.Sources; - sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() + options.Authority = identityUrl; + options.RequireHttpsMetadata = false; + options.Audience = "webshoppingagg"; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; + }); + + return services; + } + public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) + { + services.AddOptions(); + services.Configure(configuration.GetSection("urls")); + + services.AddControllers() + .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); + + services.AddSwaggerGen(options => + { + //options.DescribeAllEnumsAsStrings(); + + options.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Shopping Aggregator for Web Clients", + Version = "v1", + Description = "Shopping Aggregator for Web Clients" + }); + + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { - Optional = true, - Path = "appsettings.localhost.json", - ReloadOnChange = false + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows() + { + Implicit = new OpenApiOAuthFlow() + { + AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), + TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), + + Scopes = new Dictionary() + { + { "webshoppingagg", "Shopping Aggregator for Web Clients" } + } + } + } }); - }) - .UseStartup() - .UseSerilog((builderContext, config) => + + options.OperationFilter(); + }); + + services.AddCors(options => { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); \ No newline at end of file + options.AddPolicy("CorsPolicy", + builder => builder + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + }); + + return services; + } + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + //register delegating handlers + services.AddTransient(); + services.AddSingleton(); + + //register http services + + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs deleted file mode 100644 index fbe5dbfaf..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ /dev/null @@ -1,196 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); - - services.AddCustomMvc(Configuration) - .AddCustomAuthentication(Configuration) - //.AddCustomAuthorization(Configuration) - .AddApplicationServices() - .AddGrpcServices(); - } - - // 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) - { - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseSwagger().UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); - - c.OAuthClientId("webshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("web shopping bff Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - } -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("urls:identity"); - services.AddAuthentication("Bearer") - .AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "webshoppingagg"; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateAudience = false - }; - }); - - return services; - } - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration.GetSection("urls")); - - services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - //options.DescribeAllEnumsAsStrings(); - - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Web Clients", - Version = "v1", - Description = "Shopping Aggregator for Web Clients" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), - - Scopes = new Dictionary() - { - { "webshoppingagg", "Shopping Aggregator for Web Clients" } - } - } - } - }); - - options.OperationFilter(); - }); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - public static IServiceCollection AddApplicationServices(this IServiceCollection services) - { - //register delegating handlers - services.AddTransient(); - services.AddSingleton(); - - //register http services - - services.AddHttpClient() - .AddHttpMessageHandler(); - - return services; - } - - public static IServiceCollection AddGrpcServices(this IServiceCollection services) - { - services.AddTransient(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); - }).AddInterceptor(); - - return services; - } -}