/* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Options; using Rms.Service.Bootstrap.Health; using Rms.Service.Bootstrap.Logging; using Rms.Service.Bootstrap.Metrics; using Rms.Service.Bootstrap.Security; using System.Reflection; namespace Rms.Service.Bootstrap; /// /// Easy service initialization. Applies most of UseXX and AddXXX calls. /// public static class ServiceBootstrap { /// /// Configure standard endpoint. This enables Swagger, OpenTelemetry, gRPC, web endpoints etc. /// /// /// public static WebApplicationBuilder ConfigureStandardEndpoint(this WebApplicationBuilder builder) where T : class => builder.ConfigureStandardEndpoint(new()); /// /// Configure standard endpoint. This enables Swagger, OpenTelemetry, gRPC, web endpoints etc. /// public static WebApplicationBuilder ConfigureStandardEndpoint(this WebApplicationBuilder builder, ServiceBootstrapOptions options) where T : class { var apiVersion = options.ApiVersion; var asm = typeof(T).Assembly; var serviceName = Path.GetFileNameWithoutExtension(asm.Location); var settings = GetSecuritySettings(); builder.AddStandardOptions( asm ); // reset mTLS flag if none of the service URLs are HTTPs if ( !builder.IsHttps() ) options.EnableMTLS = false; builder .ConfigureStandardHealthChecks(options) .ConfigureStandardLogging() ; // Additional configuration is required to successfully run gRPC on macOS. // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 // Add services to the container. if ( options.EnableGrpc ) { var grpc = builder.Services.AddGrpc(); // <-- use this if you want gRPC if ( options.EnableGrpcTranscoding ) grpc.AddJsonTranscoding(); // <-- add this for JSON API to gRPC translation layer (expose gRPC via REST) } builder.Services .AddHttpClient() .AddMvc(o => // <-- this is to use REST API via Controllers { if ( options.EnableOpenIdConnect ) { // The RequireAuthenticatedUser policy locks down the entire site by default. // this disables metrics, ping, swagger and ability to use AllowAnonymous // var policy = new AuthorizationPolicyBuilder() // .RequireAuthenticatedUser() // .Build(); // o.Filters.Add(new AuthorizeFilter(policy)); } else { o.EnableEndpointRouting = false; } }); // add support for OpenAPI builder.Services .AddGrpcSwagger() .AddSwaggerGen(c => { c.SwaggerDoc(apiVersion, new() { Title = $"{serviceName} gRPC transcoding", Version = apiVersion }); var filePath = Path.Combine(AppContext.BaseDirectory, $"{serviceName}.xml"); c.IncludeXmlComments(filePath); c.IncludeGrpcXmlComments(filePath, includeControllerXmlComments: true); }); // support for OpenTelemetry builder.ConfigureStandardMetrics(options); // builder.Services .AddStandardSecurity(settings, options); return builder; } internal static IOptions GetSecuritySettings() where T : class { var tempBuilder = WebApplication.CreateBuilder(); tempBuilder.AddStandardOptions( typeof(T).Assembly ); tempBuilder.Services .AddSingleton() ; var tempServiceProvider = tempBuilder.Build(); var settings = tempServiceProvider.Services.GetRequiredService>(); return settings; } private static IHostApplicationBuilder AddStandardOptions( this IHostApplicationBuilder builder, Assembly asm ) { var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environments.Development; var serviceName = Path.GetFileNameWithoutExtension(asm.Location); builder.Configuration.Sources.Clear(); var cfg = builder.Configuration .SetBasePath(AppContext.BaseDirectory) ; var settingsLocation = Environment.GetEnvironmentVariable("SETTINGS_LOCATION") ?? ""; if ( !string.IsNullOrWhiteSpace(settingsLocation) && Directory.Exists(settingsLocation) ) { cfg .AddJsonFile(Path.Combine(settingsLocation, $"{serviceName}.Settings.json"), optional: true, reloadOnChange: true) .AddJsonFile(Path.Combine(settingsLocation, $"{serviceName}.Settings.{env}.json"), optional: true, reloadOnChange: true) ; } else { cfg .AddJsonFile(Path.Combine(AppContext.BaseDirectory, $"{serviceName}.Settings.json"), optional: true, reloadOnChange: true) .AddJsonFile(Path.Combine(AppContext.BaseDirectory, $"{serviceName}.Settings.{env}.json"), optional: true, reloadOnChange: true) ; } cfg .AddUserSecrets(asm, true) .AddEnvironmentVariables() ; // https://stackoverflow.com/questions/74346677/using-options-pattern-for-settings-net-core-app-not-working-settings-file-alwa builder.Services .ConfigureProtected(builder.Configuration.GetSection("SecuritySettings")) .Configure(builder.Configuration.GetSection("HealthCheckServiceOptions")) .AddOptions() ; return builder; } /// /// Configure Kestrel to use server certificate etc. /// /// /// /// public static KestrelServerOptions ConfigureStandardKestrel( this KestrelServerOptions kestrelServerOptions, WebApplicationBuilder builder ) where T : class => ConfigureStandardKestrel(kestrelServerOptions, builder, new()); /// /// Check if service URLs contain at least one HTTPs URL /// /// /// public static bool IsHttps(this WebApplicationBuilder builder) { var u = builder.Configuration.GetSection("ASPNETCORE_URLS").Get(); var isHttps= (u ?? builder.WebHost.GetSetting(WebHostDefaults.ServerUrlsKey) ?? string.Empty) .Split(";", StringSplitOptions.RemoveEmptyEntries) .Any(x => x.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) ; return isHttps; } /// /// Configure Kestrel to use server certificate etc. /// /// /// /// /// public static KestrelServerOptions ConfigureStandardKestrel( this KestrelServerOptions kestrelServerOptions, WebApplicationBuilder builder, ServiceBootstrapOptions bootstrapOptions ) where T : class { if (builder.IsHttps()) { kestrelServerOptions .ConfigureServerCertificate(builder) .ConfigureHttpsDefaults(o => { builder.ConfigureStandardHttpsDefaults(o, bootstrapOptions.EnableMTLS); }); } return kestrelServerOptions; } /// /// Initializes standard endpoint. This enables Swagger, OpenTelemetry, gRPC, web endpoints etc. /// /// /// /// public static WebApplication UseStandardEndpoint(this WebApplication app) where T : class => app.UseStandardEndpoint(new()); /// /// Initializes standard endpoint. This enables Swagger, OpenTelemetry, gRPC, web endpoints etc. /// /// /// /// /// public static WebApplication UseStandardEndpoint(this WebApplication app, ServiceBootstrapOptions options) where T : class { Logging.Logging.LoggerFactory = app.Services.GetService()!; var serviceName = Path.GetFileNameWithoutExtension(typeof(T).Assembly.Location); var apiVersion = options.ApiVersion; app .UseRouting() ; //app.UseCors(); app .UseStandardSecurity(options); app .UseSwagger() .UseSwaggerUI(c => { c.SwaggerEndpoint($"/swagger/{apiVersion}/swagger.json", $"{serviceName} API {apiVersion}"); }) .UseOpenTelemetryPrometheusScrapingEndpoint() ; // app.MapControllerRoute( // "default", // "{controller}/{action}"); // app.MapControllers(); app.MapStandardHealthChecks(options); return app; } }