From 6f92908a811b849e9ccc9450c2765b1ee2079395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Fri, 12 Dec 2025 08:54:06 +0100 Subject: [PATCH] Added ViewContext extensions --- CHANGELOG.md | 8 ++- .../Extensions/ViewContextExtensions.cs | 60 +++++++++++++++++ .../BasicAuthenticationHandler.cs | 17 ----- .../BasicAuthenticationMiddleware.cs | 12 ---- .../Extensions/DatabaseFacadeExtensions.cs | 29 ++------ .../DbContextOptionsBuilderExtensions.cs | 66 ++++++++++++------- .../Comparer/VersionStringComparer.cs | 1 - 7 files changed, 116 insertions(+), 77 deletions(-) create mode 100644 src/AMWD.Common.AspNetCore/Extensions/ViewContextExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index fb928c3..acd42ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [AMWD.Common.MessagePack](https://git.am-wd.de/AM.WD/common/compare/msgpack/v1.0.0...HEAD) - [AMWD.Common.Test](https://git.am-wd.de/AM.WD/common/compare/test/v2.2.0...HEAD) -_no changes_ +### Added + +- Added extensions for `ViewContext` (`IsActive` and `IsAreaActive`) + +### Changed + +- `UseDatabaseProvider()` throws `DatabaseProviderException` with more specific message content ## v2.1.0, asp/v3.1.0, efc/v3.1.0, msgpack/v1.0.0, test/v2.2.0 - 2025-11-24 diff --git a/src/AMWD.Common.AspNetCore/Extensions/ViewContextExtensions.cs b/src/AMWD.Common.AspNetCore/Extensions/ViewContextExtensions.cs new file mode 100644 index 0000000..d84443f --- /dev/null +++ b/src/AMWD.Common.AspNetCore/Extensions/ViewContextExtensions.cs @@ -0,0 +1,60 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + /// + /// Provides extension methods for the . + /// + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public static class ViewContextExtensions + { + /// + /// Determines whether the current view context matches the specified controller, action, and area names. + /// + /// + /// This method is commonly used in web applications to determine whether a navigation element should be marked as active based on the current route data. + /// + /// The containing routing information for the current request. + /// The name of the controller to compare against the current route. + /// + /// The name of the action to compare against the current route. + /// If , the action is not considered in the comparison. + /// + /// + /// The name of the area to compare against the current route. + /// If , the area is not considered in the comparison. + /// + public static bool IsActive(this ViewContext viewContext, string controller, string action = null, string area = null) + { + string currentController = viewContext.RouteData.Values["Controller"]?.ToString() ?? ""; + string currentAction = viewContext.RouteData.Values["Action"]?.ToString() ?? ""; + string currentArea = viewContext.RouteData.Values["Area"]?.ToString() ?? ""; + + if (!string.IsNullOrWhiteSpace(area) && !string.Equals(currentArea, area, StringComparison.OrdinalIgnoreCase)) + return false; + + if (!string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase)) + return false; + + if (!string.IsNullOrWhiteSpace(action) && !string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase)) + return false; + + return true; + } + + /// + /// Determines whether the specified area is the active area in the current view context. + /// + /// + /// This method is typically used in web applications to highlight navigation elements or perform logic based on the active area. + /// + /// The containing routing information for the current request. + /// The name of the area to check for activity. + public static bool IsAreaActive(this ViewContext viewContext, string area) + { + string currentArea = viewContext.RouteData.Values["Area"]?.ToString(); + + return string.Equals(currentArea, area, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs b/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs index da20985..85a979d 100644 --- a/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs +++ b/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Options; namespace AMWD.Common.AspNetCore.Security.BasicAuthentication { -#if NET8_0_OR_GREATER /// /// Implements the for Basic Authentication. /// @@ -27,22 +26,6 @@ namespace AMWD.Common.AspNetCore.Security.BasicAuthentication [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IBasicAuthenticationValidator validator) : AuthenticationHandler(options, logger, encoder) -#else - /// - /// Implements the for Basic Authentication. - /// - /// - /// Initializes a new instance of the class. - /// - /// The monitor for the options instance. - /// The . - /// The . - /// The . - /// An basic autentication validator implementation. - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public class BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IBasicAuthenticationValidator validator) - : AuthenticationHandler(options, logger, encoder, clock) -#endif { private readonly ILogger _logger = logger.CreateLogger(); private readonly IBasicAuthenticationValidator _validator = validator; diff --git a/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs b/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs index ca7475d..5ab3c7e 100644 --- a/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs +++ b/src/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationMiddleware.cs @@ -29,27 +29,15 @@ namespace AMWD.Common.AspNetCore.Security.BasicAuthentication /// An awaitable task. public async Task InvokeAsync(HttpContext httpContext) { -#if NET8_0_OR_GREATER if (!httpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue)) { SetAuthenticateRequest(httpContext, _validator.Realm); return; } -#else - if (!httpContext.Request.Headers.ContainsKey("Authorization")) - { - SetAuthenticateRequest(httpContext, _validator.Realm); - return; - } -#endif try { -#if NET8_0_OR_GREATER var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue); -#else - var authHeader = AuthenticationHeaderValue.Parse(httpContext.Request.Headers["Authorization"]); -#endif byte[] decoded = Convert.FromBase64String(authHeader.Parameter); string plain = Encoding.UTF8.GetString(decoded); diff --git a/src/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs b/src/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs index dbcb0b2..e0befc4 100644 --- a/src/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs +++ b/src/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs @@ -15,11 +15,7 @@ namespace Microsoft.EntityFrameworkCore /// /// Extensions for the . /// -#if NET8_0_OR_GREATER public static partial class DatabaseFacadeExtensions -#else - public static class DatabaseFacadeExtensions -#endif { /// /// Applies migration files to the database. @@ -31,8 +27,7 @@ namespace Microsoft.EntityFrameworkCore [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208")] public static async Task ApplyMigrationsAsync(this DatabaseFacade database, Action optionsAction, CancellationToken cancellationToken = default) { - if (database == null) - throw new ArgumentNullException(nameof(database)); + ArgumentNullException.ThrowIfNull(database); if (database.GetProviderType() == DatabaseProvider.InMemory) return true; @@ -77,8 +72,7 @@ namespace Microsoft.EntityFrameworkCore /// An awaitable task to wait until the database is available. public static async Task WaitAvailableAsync(this DatabaseFacade database, Action optionsAction = null, CancellationToken cancellationToken = default) { - if (database == null) - throw new ArgumentNullException(nameof(database)); + ArgumentNullException.ThrowIfNull(database); if (database.GetProviderType() == DatabaseProvider.InMemory) return; @@ -129,15 +123,20 @@ namespace Microsoft.EntityFrameworkCore { if (provider.Contains("mysql", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.MySQL; + if (provider.Contains("oracle", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.Oracle; + if (provider.Contains("npgsql", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.PostgreSQL; + if (provider.Contains("sqlite", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.SQLite; + if (provider.Contains("sqlclient", StringComparison.OrdinalIgnoreCase) || provider.Contains("sqlserver", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.SQLServer; + if (provider.Contains("inmemory", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.InMemory; @@ -274,11 +273,7 @@ END;" { using var stream = options.SourceAssembly.GetManifestResourceStream(migrationFile); using var sr = new StreamReader(stream); -#if NET8_0_OR_GREATER sqlScript = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); -#else - sqlScript = await sr.ReadToEndAsync().ConfigureAwait(false); -#endif } if (string.IsNullOrWhiteSpace(sqlScript)) @@ -324,11 +319,7 @@ END;" { int affectedRows = 0; // Split script by a single slash in a line -#if NET8_0_OR_GREATER string[] parts = FindSingleSlashInLine().Split(text); -#else - string[] parts = Regex.Split(text, @"\r?\n[ \t]*/[ \t]*\r?\n"); -#endif foreach (string part in parts) { // Make writable copy @@ -337,11 +328,7 @@ END;" // Remove the trailing semicolon from commands where they're not supported // (Oracle doesn't like semicolons. To keep the semicolon, it must be directly // preceeded by "end".) -#if NET8_0_OR_GREATER pt = FindEndCommand().Replace(pt.TrimEnd(), ""); -#else - pt = Regex.Replace(pt, @"(?The with applied settings. public static DbContextOptionsBuilder UseDatabaseProvider(this DbContextOptionsBuilder optionsBuilder, IConfiguration configuration, Action optionsAction = null) { -#if NET8_0_OR_GREATER ArgumentNullException.ThrowIfNull(optionsBuilder); ArgumentNullException.ThrowIfNull(configuration); -#else - if (optionsBuilder == null) - throw new ArgumentNullException(nameof(optionsBuilder)); - if (configuration == null) - throw new ArgumentNullException(nameof(configuration)); -#endif var options = new DatabaseProviderOptions(); optionsAction?.Invoke(options); @@ -49,43 +42,47 @@ namespace Microsoft.EntityFrameworkCore string connectionString = GetConnectionString(configuration, options); string provider = configuration.GetValue("provider")?.ToLower(); - var builderType = GetBuilderType(configuration); - var extensionType = GetExtensionType(configuration); + var builderType = GetBuilderType(configuration) + ?? throw new DatabaseProviderException($"Could not find the DbContextOptionsBuilder for provider: {provider}"); + + var extensionType = GetExtensionType(configuration) + ?? throw new DatabaseProviderException($"Could not find the DbContextOptionsBuilder extensions for provider: {provider}"); + var actionType = typeof(Action<>).MakeGenericType(builderType); - object serverVersion = null; MethodInfo methodInfo; + object serverVersion = null; switch (provider) { case "mysql": - methodInfo = extensionType.GetMethod("UseMySql", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseMySql", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); if (methodInfo == null) - methodInfo = extensionType.GetMethod("UseMySQL", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseMySQL", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); if (methodInfo == null) // Pomelo MySQL v5 { var serverVersionType = Type.GetType("Microsoft.EntityFrameworkCore.ServerVersion, Pomelo.EntityFrameworkCore.MySql"); - var autoDetectMethodInfo = serverVersionType.GetMethod("AutoDetect", new Type[] { typeof(string) }); - methodInfo = extensionType.GetMethod("UseMySql", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), serverVersionType, actionType }); - serverVersion = autoDetectMethodInfo.Invoke(null, new object[] { connectionString }); + var autoDetectMethodInfo = serverVersionType.GetMethod("AutoDetect", [typeof(string)]); + methodInfo = extensionType.GetMethod("UseMySql", [typeof(DbContextOptionsBuilder), typeof(string), serverVersionType, actionType]); + serverVersion = autoDetectMethodInfo.Invoke(null, [connectionString]); } break; case "oracle": - methodInfo = extensionType.GetMethod("UseOracle", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseOracle", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); break; case "postgres": case "postgresql": - methodInfo = extensionType.GetMethod("UseNpgsql", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseNpgsql", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); break; case "sqlite": - methodInfo = extensionType.GetMethod("UseSqlite", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseSqlite", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); break; case "sqlserver": case "mssql": - methodInfo = extensionType.GetMethod("UseSqlServer", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseSqlServer", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); break; case "memory": case "inmemory": - methodInfo = extensionType.GetMethod("UseInMemoryDatabase", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + methodInfo = extensionType.GetMethod("UseInMemoryDatabase", [typeof(DbContextOptionsBuilder), typeof(string), actionType]); break; default: throw new DatabaseProviderException($"Unknown database provider: {provider}"); @@ -93,11 +90,11 @@ namespace Microsoft.EntityFrameworkCore if (serverVersion == null) { - methodInfo?.Invoke(null, new object[] { optionsBuilder, connectionString, null }); + methodInfo?.Invoke(null, [optionsBuilder, connectionString, null]); } else { - methodInfo?.Invoke(null, new object[] { optionsBuilder, connectionString, serverVersion, null }); + methodInfo?.Invoke(null, [optionsBuilder, connectionString, serverVersion, null]); } return optionsBuilder; @@ -105,8 +102,8 @@ namespace Microsoft.EntityFrameworkCore private static Type GetBuilderType(IConfiguration configuration) { - string provider = configuration.GetValue("provider")?.ToLower(); Type builderType; + string provider = configuration.GetValue("provider")?.ToLower(); switch (provider) { case "mysql": @@ -116,34 +113,41 @@ namespace Microsoft.EntityFrameworkCore if (builderType == null) // as MySql.Data.EntityFrameworkCore is marked as deprecated on NuGet builderType = Type.GetType("MySql.EntityFrameworkCore.Infrastructure.MySQLDbContextOptionsBuilder, MySql.EntityFrameworkCore"); break; + case "oracle": builderType = Type.GetType("Oracle.EntityFrameworkCore.Infrastructure.OracleDbContextOptionsBuilder, Oracle.EntityFrameworkCore"); break; + case "postgres": case "postgresql": builderType = Type.GetType("Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.NpgsqlDbContextOptionsBuilder, Npgsql.EntityFrameworkCore.PostgreSQL"); break; + case "sqlite": builderType = Type.GetType("Microsoft.EntityFrameworkCore.Infrastructure.SqliteDbContextOptionsBuilder, Microsoft.EntityFrameworkCore.Sqlite"); break; + case "sqlserver": case "mssql": builderType = Type.GetType("Microsoft.EntityFrameworkCore.Infrastructure.SqlServerDbContextOptionsBuilder, Microsoft.EntityFrameworkCore.SqlServer"); break; + case "memory": case "inmemory": builderType = Type.GetType("Microsoft.EntityFrameworkCore.Infrastructure.InMemoryDbContextOptionsBuilder, Microsoft.EntityFrameworkCore.InMemory"); break; + default: throw new ArgumentException($"Unknown database provider: {provider}"); } + return builderType; } private static Type GetExtensionType(IConfiguration configuration) { - string provider = configuration.GetValue("provider")?.ToLower(); Type extensionType; + string provider = configuration.GetValue("provider")?.ToLower(); switch (provider) { case "mysql": @@ -153,27 +157,34 @@ namespace Microsoft.EntityFrameworkCore if (extensionType == null) extensionType = Type.GetType("Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions, MySql.EntityFrameworkCore"); break; + case "oracle": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.OracleDbContextOptionsBuilderExtensions, Oracle.EntityFrameworkCore"); break; + case "postgres": case "postgresql": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsBuilderExtensions, Npgsql.EntityFrameworkCore.PostgreSQL"); break; + case "sqlite": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.SqliteDbContextOptionsBuilderExtensions, Microsoft.EntityFrameworkCore.Sqlite"); break; + case "sqlserver": case "mssql": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.SqlServerDbContextOptionsExtensions, Microsoft.EntityFrameworkCore.SqlServer"); break; + case "memory": case "inmemory": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.InMemoryDbContextOptionsExtensions, Microsoft.EntityFrameworkCore.InMemory"); break; + default: throw new ArgumentException($"Unknown database provider: {provider}"); } + return extensionType; } @@ -191,12 +202,14 @@ namespace Microsoft.EntityFrameworkCore cs.Add($"Password={configuration.GetValue("Password")}"); cs.Add($"Connection Timeout=15"); break; + case "oracle": cs.Add($"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={configuration.GetValue("Host")})(PORT={configuration.GetValue("Port", 1521)}))(CONNECT_DATA=(SERVICE_NAME={configuration.GetValue("Name")})))"); cs.Add($"User Id={configuration.GetValue("Username")}"); cs.Add($"Password={configuration.GetValue("Password")}"); cs.Add($"Connection Timeout=15"); break; + case "postgres": case "postgresql": cs.Add($"Server={configuration.GetValue("Host")}"); @@ -207,6 +220,7 @@ namespace Microsoft.EntityFrameworkCore cs.Add($"Password={configuration.GetValue("Password")}"); cs.Add($"Timeout=15"); break; + case "sqlite": string path = configuration.GetValue("File"); if (!Path.IsPathRooted(path)) @@ -219,6 +233,7 @@ namespace Microsoft.EntityFrameworkCore cs.Add($"Data Source={path}"); cs.Add("Foreign Keys=True"); break; + case "sqlserver": case "mssql": cs.Add($"Server={configuration.GetValue("Host")},{configuration.GetValue("Port", 1433)}"); @@ -235,13 +250,16 @@ namespace Microsoft.EntityFrameworkCore } cs.Add("Connect Timeout=15"); break; + case "memory": case "inmemory": cs.Add(configuration.GetValue("Name", provider)); break; + default: throw new DatabaseProviderException($"Unknown database provider: {provider}"); } + return string.Join(";", cs); } } diff --git a/src/AMWD.Common/Comparer/VersionStringComparer.cs b/src/AMWD.Common/Comparer/VersionStringComparer.cs index caf0f38..dc44b48 100644 --- a/src/AMWD.Common/Comparer/VersionStringComparer.cs +++ b/src/AMWD.Common/Comparer/VersionStringComparer.cs @@ -12,7 +12,6 @@ namespace AMWD.Common.Comparer { private readonly Regex _versionRegex = VersionRegex(); #else - public class VersionStringComparer : IComparer { private readonly Regex _versionRegex = new("([0-9.]+)", RegexOptions.Compiled);