From d363b90be5696cd14bdb5984115262954afe16ed Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Tue, 19 Nov 2024 00:08:31 +0100 Subject: [PATCH 1/3] Refactoring Extensions --- .../Extensions/ApplicationExtensions.cs | 27 +++- .../Extensions/ServiceCollectionExtensions.cs | 137 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/GSWCloudApp.Common/Extensions/ServiceCollectionExtensions.cs diff --git a/src/GSWCloudApp.Common/Extensions/ApplicationExtensions.cs b/src/GSWCloudApp.Common/Extensions/ApplicationExtensions.cs index 799f23b..ddf3c5b 100644 --- a/src/GSWCloudApp.Common/Extensions/ApplicationExtensions.cs +++ b/src/GSWCloudApp.Common/Extensions/ApplicationExtensions.cs @@ -1,12 +1,37 @@ -using GSWCloudApp.Common.Options; +using Asp.Versioning; +using GSWCloudApp.Common.Options; +using GSWCloudApp.Common.Vault.Options; +using GSWCloudApp.Common.Vault.Service; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Hosting; namespace GSWCloudApp.Common.Extensions; public static class ApplicationExtensions { + public static async Task GetVaultStringConnectionAsync(WebApplicationBuilder builder, string vaultPath, string vaultKey) + { + var vaultOptions = builder.Services.ConfigureAndGet(builder.Configuration, nameof(VaultOptions)) + ?? throw new InvalidOperationException("Vault options not found."); + + return await VaultService.ReadVaultSecretAsync(vaultOptions, vaultPath, vaultKey); + } + + public static RouteGroupBuilder UseVersioningApi(WebApplication app) + { + var apiVersionSet = app.NewApiVersionSet() + .HasApiVersion(new ApiVersion(1)) + .Build(); + + var versionedApi = app + .MapGroup("/api/v{version:apiVersion}") + .WithApiVersionSet(apiVersionSet); + + return versionedApi; + } + public static void UseDevSwagger(this WebApplication app, ApplicationOptions options) { if (app.Environment.IsDevelopment() || options.SwaggerEnable) diff --git a/src/GSWCloudApp.Common/Extensions/ServiceCollectionExtensions.cs b/src/GSWCloudApp.Common/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..04a91da --- /dev/null +++ b/src/GSWCloudApp.Common/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,137 @@ +using Asp.Versioning; +using AutoMapper; +using FluentValidation; +using GSWCloudApp.Common.Options; +using GSWCloudApp.Common.RedisCache; +using GSWCloudApp.Common.RedisCache.Options; +using GSWCloudApp.Common.Service; +using GSWCloudApp.Common.Swagger; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace GSWCloudApp.Common.Extensions; + +public static class ServiceExtensions +{ + public static IServiceCollection ConfigureDbContextAsync(this IServiceCollection services, string databaseConnection, + ApplicationOptions applicationOptions) where T : class + where TDbContext : DbContext + { + var assembly = typeof(T).Assembly.GetName().Name!.ToString(); + var AssemblyMigrazioni = string.Concat(assembly, ".Migrations"); + + services.AddDbContext(optionsBuilder => + { + optionsBuilder.UseNpgsql(databaseConnection, options => + { + options.MigrationsAssembly(AssemblyMigrazioni) + .MigrationsHistoryTable(applicationOptions.TabellaMigrazioni) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + .EnableRetryOnFailure(3, TimeSpan.FromSeconds(5), null); + }); + }); + + return services; + } + + public static IServiceCollection ConfigureCors(this IServiceCollection services, string policyName) + { + return services.AddCors(options => + { + options.AddPolicy(policyName, builder + => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + }); + } + + public static IServiceCollection ConfigureApiVersioning(this IServiceCollection services) + { + services.AddApiVersioning(options => + { + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + options.DefaultApiVersion = new ApiVersion(1); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ReportApiVersions = true; + }) + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); + + return services; + } + + public static IServiceCollection ConfigureSwagger(this IServiceCollection services) + { + return services + .AddEndpointsApiExplorer() + .AddSwaggerGen(options => + { + options.EnableAnnotations(); + options.OperationFilter(); + }) + .ConfigureOptions(); + } + + public static IServiceCollection ConfigureProblemDetails(this IServiceCollection services) + { + return services.AddProblemDetails(options => + { + options.CustomizeProblemDetails = context => + { + context.ProblemDetails.Instance = $"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}"; + + var activity = context.HttpContext.Features.Get()?.Activity; + context.ProblemDetails.Extensions.TryAdd("traceId", activity?.Id); + //context.ProblemDetails.Extensions.TryAdd("requestId", context.HttpContext.TraceIdentifier); + }; + }); + } + + public static IServiceCollection ConfigureRedisCache(this IServiceCollection services, IConfiguration configuration, string redisConnection) + { + var options = services.ConfigureAndGet(configuration, nameof(RedisOptions)) + ?? throw new InvalidOperationException("Redis options not found in configuration."); + + options.Hostname = redisConnection; + + return services.AddStackExchangeRedisCache(action => + { + action.Configuration = options.Hostname; + action.InstanceName = options.InstanceName; + }); + } + + public static IServiceCollection ConfigureServices(this IServiceCollection services) + where TDbContext : DbContext + where TMappingProfile : Profile + where TValidator : IValidator + { + services.AddAntiforgery(); + services.AddAutoMapper(typeof(TMappingProfile).Assembly); + services.AddValidatorsFromAssemblyContaining(); + + // Service Registrations with Singleton Lifecycle + services.AddSingleton(); + + // Service Registrations with Transient Lifecycle + services.AddTransient(); + + // Service Registrations with Singleton Lifecycle + services.AddScoped(); + + return services; + } + + public static IServiceCollection ConfigureOptions(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(options => options.LowercaseUrls = true); + services.Configure(configuration.GetSection("Kestrel")); + + return services; + } +} \ No newline at end of file From b6208bda9ce607761b9ec710f19601d0f05ae712 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Tue, 19 Nov 2024 00:08:42 +0100 Subject: [PATCH 2/3] Refactoring Helpers --- .../Helpers/ApplicationHelpers.cs | 52 +++++++++++++++++++ .../Helpers/DatabaseHelpers.cs | 12 +++++ 2 files changed, 64 insertions(+) create mode 100644 src/GSWCloudApp.Common/Helpers/ApplicationHelpers.cs create mode 100644 src/GSWCloudApp.Common/Helpers/DatabaseHelpers.cs diff --git a/src/GSWCloudApp.Common/Helpers/ApplicationHelpers.cs b/src/GSWCloudApp.Common/Helpers/ApplicationHelpers.cs new file mode 100644 index 0000000..14c9704 --- /dev/null +++ b/src/GSWCloudApp.Common/Helpers/ApplicationHelpers.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace GSWCloudApp.Common.Helpers; + +public static class ApplicationHelpers +{ + public static async Task ConfigureDatabaseAsync(IServiceProvider serviceProvider) where TDbContext : DbContext + { + using var scope = serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + if (!dbContext.Database.IsInMemory()) + { + await EnsureDatabaseAsync(); + await RunMigrationsAsync(); + } + else + { + dbContext.Database.EnsureCreated(); + } + + async Task EnsureDatabaseAsync() + { + var dbCreator = dbContext.GetService(); + var strategy = dbContext.Database.CreateExecutionStrategy(); + + await strategy.ExecuteAsync(async () => + { + if (!await dbCreator.ExistsAsync()) + { + await dbCreator.CreateAsync(); + } + }); + } + + async Task RunMigrationsAsync() + { + var strategy = dbContext.Database.CreateExecutionStrategy(); + + await strategy.ExecuteAsync(async () => + { + await using var transaction = await dbContext.Database.BeginTransactionAsync(); + + await dbContext.Database.MigrateAsync(); + await transaction.CommitAsync(); + }); + } + } +} \ No newline at end of file diff --git a/src/GSWCloudApp.Common/Helpers/DatabaseHelpers.cs b/src/GSWCloudApp.Common/Helpers/DatabaseHelpers.cs new file mode 100644 index 0000000..1e35a0e --- /dev/null +++ b/src/GSWCloudApp.Common/Helpers/DatabaseHelpers.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace GSWCloudApp.Common.Helpers; + +public static class DatabaseHelpers +{ + public static void ExecuteSQLScriptFromAssembly(this MigrationBuilder migrationBuilder, string assetName) + { + var sqlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQLScripts", assetName); + migrationBuilder.Sql(File.ReadAllText(sqlFile)); + } +} \ No newline at end of file From c9c54946ad585c45b50adfd35bea646af4f5d246 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Tue, 19 Nov 2024 00:08:57 +0100 Subject: [PATCH 3/3] Refactoring --- src/GSWCloudApp.Common/GSWCloudApp.Common.csproj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/GSWCloudApp.Common/GSWCloudApp.Common.csproj b/src/GSWCloudApp.Common/GSWCloudApp.Common.csproj index 5a1c3c5..f42df3e 100644 --- a/src/GSWCloudApp.Common/GSWCloudApp.Common.csproj +++ b/src/GSWCloudApp.Common/GSWCloudApp.Common.csproj @@ -17,14 +17,20 @@ - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + +