From 46b32af3b7b52ddfec180fe01d596759cbc4ee95 Mon Sep 17 00:00:00 2001 From: axunonb Date: Mon, 13 Feb 2023 20:13:13 +0100 Subject: [PATCH] Bump version to v3.5.0 * Migrate startup from Host.CreateDefaultBuilder to WebApplicationBuilder * ASP.NET Core 3.1 used the Generic Host for Startup. This is compatible with NET6.0. NET6.0 now recommends the new minimal hosting model with WebApplicationBuilder. * Most configuration now happens in WebApplicationBuilder and static helper classe * Fix NLog.{Environment}.config: Add to targets: keepFileOpen="true" openFileCacheTimeout="5" concurrentWrites="true" This will stop log files to be blocked while the app is running * Update to PiranhaCMS 10.3.0 * Update referenced packages * Microsoft.Extensions.Logging.Abstractions 7.0.0 * NLog.Web.AspNetCore 5.2.1 * EPPlus 6.1.2 * MailKit 3.5.0 * Microsoft.Data.SqlClient 5.1.0 * Microsoft.EntityFrameworkCore 7.0.2 * Microsoft.EntityFrameworkCore.Tools 7.0.2 --- ClubSite/Areas/Manager/Pages/Login.cshtml | 14 +- ClubSite/ClubSite.csproj | 38 +-- .../Configuration/NLog.Development.config | 6 +- ClubSite/Configuration/NLog.Production.config | 6 +- ClubSite/Program.cs | 107 ++++---- ClubSite/{Startup.cs => WebAppStartup.cs} | 238 +++++++++--------- 6 files changed, 206 insertions(+), 203 deletions(-) rename ClubSite/{Startup.cs => WebAppStartup.cs} (66%) diff --git a/ClubSite/Areas/Manager/Pages/Login.cshtml b/ClubSite/Areas/Manager/Pages/Login.cshtml index 7e8b69d..aa920e2 100644 --- a/ClubSite/Areas/Manager/Pages/Login.cshtml +++ b/ClubSite/Areas/Manager/Pages/Login.cshtml @@ -1,10 +1,10 @@ @page "~/manager/login" @model Piranha.Manager.LocalAuth.Areas.Manager.Pages.LoginModel -@inject Piranha.Manager.ManagerLocalizer Localizer +@inject ManagerLocalizer Localizer @{ Layout = null; - var module = Piranha.App.Modules.Get(); - var prerelease = Piranha.Utils.IsPreRelease(typeof(Piranha.Manager.Module).Assembly) ? "pre-release" : ""; + var module = Piranha.App.Modules.Get(); + var preRelease = Piranha.Utils.IsPreRelease(typeof(Module).Assembly) ? "pre-release" : ""; } @@ -20,8 +20,8 @@ @Localizer.General["Login"] - - @if (!string.IsNullOrEmpty(prerelease)) + + @if (!string.IsNullOrEmpty(preRelease)) {
Pre-release
} @@ -33,7 +33,7 @@ @@ -58,7 +58,7 @@
-

axuno ClubSite, @Localizer.General["Version"] @Piranha.Utils.GetAssemblyVersion(typeof(ClubSite.Startup).Assembly)

+

axuno ClubSite, @Localizer.General["Version"] @Piranha.Utils.GetAssemblyVersion(typeof(ClubSite.Program).Assembly)

Design und Realisierung:
Norbert Bietsch, axuno gemeinnützige GmbH diff --git a/ClubSite/ClubSite.csproj b/ClubSite/ClubSite.csproj index 7acb3a1..a008589 100644 --- a/ClubSite/ClubSite.csproj +++ b/ClubSite/ClubSite.csproj @@ -4,7 +4,7 @@ net6.0 enable latest - 3.4.0 + 3.5.0 axuno gGmbH The source code for https://www.volleyballclub.de/ $([System.DateTime]::Now.ToString(yyyy)) @@ -13,28 +13,28 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/ClubSite/Configuration/NLog.Development.config b/ClubSite/Configuration/NLog.Development.config index bf3c0c0..2ccfe8d 100644 --- a/ClubSite/Configuration/NLog.Development.config +++ b/ClubSite/Configuration/NLog.Development.config @@ -14,15 +14,15 @@ - - - diff --git a/ClubSite/Configuration/NLog.Production.config b/ClubSite/Configuration/NLog.Production.config index caaf51f..decc258 100644 --- a/ClubSite/Configuration/NLog.Production.config +++ b/ClubSite/Configuration/NLog.Production.config @@ -14,15 +14,15 @@ - - - diff --git a/ClubSite/Program.cs b/ClubSite/Program.cs index bb89860..ad83868 100644 --- a/ClubSite/Program.cs +++ b/ClubSite/Program.cs @@ -5,11 +5,14 @@ using System; using System.IO; -using Microsoft.AspNetCore; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; using NLog.Web; namespace ClubSite; @@ -22,7 +25,7 @@ public class Program /// public const string ConfigurationFolder = "Configuration"; - public static void Main(string[] args) + public static async Task Main(string[] args) { // NLog: setup the logger first to catch all errors var currentDir = Directory.GetCurrentDirectory(); @@ -31,15 +34,32 @@ public static void Main(string[] args) .GetCurrentClassLogger(); // Allows for - NLog.LogManager.Configuration.Variables["logDirectory"] = currentDir + Path.DirectorySeparatorChar; + NLog.LogManager.Configuration.Variables["logDirectory"] = $"{currentDir}{Path.DirectorySeparatorChar}"; try { - logger.Trace($"Configuration of {nameof(WebHost)} starting."); - // http://zuga.net/articles/cs-how-to-determine-if-a-program-process-or-file-is-32-bit-or-64-bit/ - logger.Info($"This app runs as {(System.Environment.Is64BitProcess ? "64-bit" : "32-bit")} process.\n\n"); + logger.Trace($"Configuration of {nameof(Microsoft.AspNetCore.WebHost)} starting."); + logger.Info($"This app runs as {(Environment.Is64BitProcess ? "64-bit" : "32-bit")} process.\n\n"); - CreateHostBuilder(args).Build().Run(); + var builder = SetupBuilder(args); + + var loggingConfig = builder.Configuration.GetSection("Logging"); + builder.Logging.ClearProviders(); + // Enable NLog as logging provider for Microsoft.Extension.Logging + builder.Logging.AddNLog(loggingConfig); + NLogBuilder.ConfigureNLog(Path.Combine(builder.Environment.ContentRootPath, ConfigurationFolder, + $"NLog.{builder.Environment.EnvironmentName}.config")); + + builder.WebHost.ConfigureServices(WebAppStartup.ConfigureServices); + + var app = builder.Build(); + + builder.WebHost.ConfigureAppConfiguration((context, confBuilder) => + { + WebAppStartup.Configure(app, app.Services.GetRequiredService()); + }); + + await app.RunAsync(); } catch (Exception e) { @@ -48,52 +68,45 @@ public static void Main(string[] args) } finally { - // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) + // Ensure to flush and stop internal timers/threads before application-exit (avoid segmentation fault on Linux) NLog.LogManager.Shutdown(); } } - public static IHostBuilder CreateHostBuilder(string[] args) + public static WebApplicationBuilder SetupBuilder(string[] args) { - return Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((hostingContext, config) => - { - var configPath = Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, - ConfigurationFolder); - config.SetBasePath(configPath) - .AddJsonFile("appsettings.json", false, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, - true) - .AddJsonFile(@"credentials.json", false, true) - .AddJsonFile($"credentials.{hostingContext.HostingEnvironment.EnvironmentName}.json", false, - true) - .AddEnvironmentVariables() - .AddCommandLine(args); + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + Args = args, + ApplicationName = typeof(Program).Assembly.GetName().Name, // don't use Assembly.Fullname + ContentRootPath = Directory.GetCurrentDirectory(), + WebRootPath = "wwwroot" + }); + + var absoluteConfigurationPath = Path.Combine(builder.Environment.ContentRootPath, + ConfigurationFolder); - var secretsFolder = Path.Combine(configPath, @$"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}Secrets"); - if (hostingContext.HostingEnvironment.IsDevelopment()) - { - if (!Directory.Exists(secretsFolder)) - throw new DirectoryNotFoundException("Secrets folder not found"); - config.AddJsonFile(Path.Combine(secretsFolder, @"credentials.json"), false); - config.AddJsonFile( - Path.Combine(secretsFolder, - $"credentials.{hostingContext.HostingEnvironment.EnvironmentName}.json"), false); - } + builder.Configuration.SetBasePath(absoluteConfigurationPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", + optional: true, reloadOnChange: true) + .AddJsonFile(@"credentials.json", optional: false, reloadOnChange: true) + .AddJsonFile($"credentials.{builder.Environment.EnvironmentName}.json", + optional: false, reloadOnChange: true) + .AddEnvironmentVariables() + .AddCommandLine(args); - NLogBuilder.ConfigureNLog(Path.Combine(configPath, - $"NLog.{hostingContext.HostingEnvironment.EnvironmentName}.config")); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.ClearProviders(); - // Note: This logging configuration overrides any call to SetMinimumLevel! - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - }) - .UseNLog(); // NLog: Setup NLog for dependency injection; + if (builder.Environment.IsDevelopment()) + { + var secretsFolder = Path.Combine(builder.Environment.ContentRootPath, ConfigurationFolder, @$"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}Secrets"); + if (!Directory.Exists(secretsFolder)) throw new DirectoryNotFoundException("Secrets folder not found"); + builder.Configuration.AddJsonFile(Path.Combine(secretsFolder, @"credentials.json"), false); + builder.Configuration.AddJsonFile(Path.Combine(secretsFolder, $"credentials.{builder.Environment.EnvironmentName}.json"), false); + } + + // Use static web assets from League (and other referenced projects or packages) + builder.WebHost.UseStaticWebAssets(); + + return builder; } } \ No newline at end of file diff --git a/ClubSite/Startup.cs b/ClubSite/WebAppStartup.cs similarity index 66% rename from ClubSite/Startup.cs rename to ClubSite/WebAppStartup.cs index 13b08e0..3e93417 100644 --- a/ClubSite/Startup.cs +++ b/ClubSite/WebAppStartup.cs @@ -3,114 +3,63 @@ // of the MIT license. See the LICENSE file for details. // https://github.com/axuno/ClubSite -using System; using System.Globalization; using System.IO; -using ClubSite.Models; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Net.Http.Headers; -using Piranha; -using Piranha.AttributeBuilder; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Rewrite; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.EntityFrameworkCore; using Piranha.AspNetCore.Identity.SQLServer; using Piranha.Data.EF.SQLServer; +using System; +using Microsoft.AspNetCore.Http; +using ClubSite.Models; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Net.Http.Headers; +using Piranha.AttributeBuilder; using Piranha.Manager.Editor; -using Microsoft.AspNetCore.Rewrite; -using Microsoft.Extensions.Logging; -using NLog.Extensions.Logging; +using Piranha; +using Piranha.AspNetCore.Models; namespace ClubSite; -public class Startup +/// +/// The demo startup class to setup and configure the league. +/// +public static class WebAppStartup { /// - /// Gets the application configuration properties of this application. - /// - public IConfiguration Configuration { get; } - - /// - /// Gets the information about the web hosting environment of this application. + /// The method gets called by at startup, BEFORE building the app is completed. /// - public IWebHostEnvironment WebHostEnvironment { get; } - - /// - /// Gets the logger for class . - /// - public ILogger Logger { get; } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - /// - /// - public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) + public static void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) { - Configuration = configuration; - WebHostEnvironment = webHostEnvironment; - - var loggerFactory = LoggerFactory.Create(builder => - { - builder.AddNLog(); - }); - - Logger = loggerFactory.CreateLogger(); - } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - // Note: Piranha sets culture and language - // from table Piranha_Sites field Culture and the UI language - // from table Piranha_Sites field LanguageId (stored in table Piranha_Languages) - // This overrides the following settings: - var cultureInfo = CultureInfo.GetCultureInfo("de-DE"); - CultureInfo.DefaultThreadCurrentCulture = - CultureInfo.CurrentCulture = cultureInfo; - CultureInfo.DefaultThreadCurrentUICulture = - CultureInfo.CurrentUICulture = cultureInfo; - // required for cookies and session cookies (will throw CryptographicException without) services.AddDataProtection() - .SetApplicationName("ClubSite") + .SetApplicationName(context.HostingEnvironment.ApplicationName) .SetDefaultKeyLifetime(TimeSpan.FromDays(360)) .PersistKeysToFileSystem( - new DirectoryInfo(Path.Combine(WebHostEnvironment.ContentRootPath, "DataProtectionKeys"))) + new DirectoryInfo(Path.Combine(context.HostingEnvironment.ContentRootPath, "DataProtectionKeys"))) .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 }); - // Make sure we can connect to the database - using (var connection = - new Microsoft.Data.SqlClient.SqlConnection(Configuration.GetConnectionString("VolleyballClub"))) - try - { - connection.Open(); - } - finally - { - connection.Close(); - } - services.AddMemoryCache(); // Adds a default in-memory cache implementation // Custom ClubSite db context services.AddDbContext((sp, options) => - options.UseSqlServer(Configuration.GetConnectionString("VolleyballClub"))); + options.UseSqlServer(context.Configuration.GetConnectionString("VolleyballClub"))); // Piranha service setup services.AddPiranha(svcBuilder => { - svcBuilder.AddRazorRuntimeCompilation = WebHostEnvironment.IsDevelopment(); + svcBuilder.AddRazorRuntimeCompilation = context.HostingEnvironment.IsDevelopment(); svcBuilder.UseCms(); svcBuilder.UseFileStorage(naming: Piranha.Local.FileStorageNaming.UniqueFolderNames); @@ -119,9 +68,9 @@ public void ConfigureServices(IServiceCollection services) svcBuilder.UseTinyMCE(); svcBuilder.UseMemoryCache(); svcBuilder.UseEF(db => - db.UseSqlServer(Configuration.GetConnectionString("VolleyballClub"))); + db.UseSqlServer(context.Configuration.GetConnectionString("VolleyballClub"))); svcBuilder.UseIdentityWithSeed(db => - db.UseSqlServer(Configuration.GetConnectionString("VolleyballClub"))); + db.UseSqlServer(context.Configuration.GetConnectionString("VolleyballClub"))); }); // MUST be before AddMvc! @@ -153,7 +102,7 @@ public void ConfigureServices(IServiceCollection services) }); services.Configure( - Configuration.GetSection(nameof(ConfigurationPoco.MailSettings)) ?? + context.Configuration.GetSection(nameof(ConfigurationPoco.MailSettings)) ?? throw new InvalidOperationException( $"Configuration section '{nameof(ConfigurationPoco.MailSettings)}' not found.")); @@ -164,59 +113,60 @@ public void ConfigureServices(IServiceCollection services) } /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// The method gets called by at startup, AFTER building the app is completed. /// - /// The - /// The - /// The PiranhaCms - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api) + public static void Configure(WebApplication app, ILoggerFactory loggerFactory) { - _ = env.EnvironmentName; app.UseHttpsRedirection(); - if (false) - app.UseDeveloperExceptionPage(); - else - { - app.UseStatusCodePagesWithReExecute($"/Error/{{0}}"); - app.UseExceptionHandler($"/Error/500"); - // instruct the browsers to always access the site via HTTPS - app.UseHsts(); - } + // Note: Piranha sets culture and language + // from table Piranha_Sites field Culture and the UI language + // from table Piranha_Sites field LanguageId (stored in table Piranha_Languages) + // This overrides the following settings: + var cultureInfo = CultureInfo.GetCultureInfo("de-DE"); + CultureInfo.DefaultThreadCurrentCulture = + CultureInfo.CurrentCulture = cultureInfo; + CultureInfo.DefaultThreadCurrentUICulture = + CultureInfo.CurrentUICulture = cultureInfo; - // Initialize Piranha - App.Init(api); + // Make sure we can connect to the database + using (var connection = + new Microsoft.Data.SqlClient.SqlConnection(app.Configuration.GetConnectionString("VolleyballClub"))) + try + { + connection.Open(); + } + catch + { + loggerFactory.CreateLogger(nameof(WebAppStartup)).LogCritical("Failed to connect: {ConnectionString}", connection.ConnectionString); + } + finally + { + connection.Close(); + } - // Build all content types in this assembly - new ContentTypeBuilder(api) - .AddAssembly(typeof(Startup).Assembly) - .Build() - .DeleteOrphans(); + var env = app.Environment; - // Register custom blocks - App.Blocks.Register(); - App.Blocks.Register(); - App.Blocks.Register(); + #region * Setup error handling * - /* To build specific types: - new Piranha.AttributeBuilder.PageTypeBuilder(api) - .AddType(typeof(Models.BlogArchive)) - .AddType(typeof(Models.StandardPage)) - .AddType(typeof(Models.TeaserPage)) - .Build() - .DeleteOrphans(); - new Piranha.AttributeBuilder.PostTypeBuilder(api) - .AddType(typeof(Models.BlogPost)) - .Build() - .DeleteOrphans(); - new Piranha.AttributeBuilder.SiteTypeBuilder(api) - .AddType(typeof(Models.StandardSite)) - .Build() - .DeleteOrphans(); - */ + // Error handling must be one of the very first things to configure + if (env.IsProduction()) + { + // The StatusCodePagesMiddleware should be one of the earliest + // middleware in the pipeline, as it can only modify the response + // of middleware that comes after it in the pipeline + app.UseStatusCodePagesWithReExecute($"/Error/{{0}}"); + app.UseExceptionHandler("/Error/500"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + else + { + app.UseDeveloperExceptionPage(); + app.UseStatusCodePages(); + } - // Configure Tiny MCE - EditorConfig.FromFile($@"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}{Program.ConfigurationFolder}{Path.DirectorySeparatorChar}editorconfig.json"); + #endregion // Keep before .UsePiranha() app.UseSession(); @@ -230,14 +180,54 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api #endregion + #region *** Configure PiranhaCMS *** + + // Configure Tiny MCE + EditorConfig.FromFile($@"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}{Program.ConfigurationFolder}{Path.DirectorySeparatorChar}editorconfig.json"); + // Middleware setup app.UsePiranha(options => { + // Initialize Piranha + App.Init(options.Api); options.UseManager(); options.UseTinyMCE(); options.UseIdentity(); + + // Build all content types in this assembly + _ = new ContentTypeBuilder(options.Api) + .AddAssembly(typeof(Program).Assembly) + .Build() + .DeleteOrphans(); + + // To build specific types: + // new ContentTypeBuilder(options.Api).AddType(typeof(...) + + /* + * Here you can configure the different permissions + * that you want to use for securing content in the + * application. + options.UseSecurity(o => + { + o.UsePermission("WebUser", "Web User"); + }); + */ + + /* + * Here you can specify the login url for the front end + * application. This does not affect the login url of + * the manager interface. + options.LoginUrl = "login"; + */ }); + // Register custom blocks + App.Blocks.Register(); + App.Blocks.Register(); + App.Blocks.Register(); + + #endregion + // For static files using a content type provider: var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); // Make sure .webmanifest files don't cause a 404 @@ -254,4 +244,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api } }); } -} \ No newline at end of file +}