From 3a032f401ac6ad7a2d96ffbec2c6f05853c78295 Mon Sep 17 00:00:00 2001 From: Charles Nurse Date: Wed, 24 Mar 2021 11:45:44 -0700 Subject: [PATCH] Added IDatabase interface and refactored to use it to handle database type - updated Installer to dynamically add databases to selector --- Oqtane.Client/Program.cs | 18 +++++++++-- Oqtane.Client/UI/Installer.razor | 21 +++++++++---- Oqtane.Server/Databases/LocalDbDatabase.cs | 16 ++++++++++ Oqtane.Server/Databases/SqlServerDatabase.cs | 17 ++++++++++ Oqtane.Server/Databases/SqliteDatabase.cs | 17 ++++++++++ .../DbContextOptionsBuilderExtensions.cs | 17 +++++----- .../OqtaneServiceCollectionExtensions.cs | 12 +++++++ .../Infrastructure/DatabaseManager.cs | 31 +++++++++++-------- .../02010001_AddDatabaseTypeColumnToTenant.cs | 2 +- .../Extensions/ColumnsBuilderExtensions.cs | 5 +-- .../Repository/Context/DBContextBase.cs | 2 -- Oqtane.Shared/Interfaces/IDatabase.cs | 13 ++++++++ Oqtane.Shared/Models/Database.cs | 9 ++++++ Oqtane.Shared/Oqtane.Shared.csproj | 1 + 14 files changed, 145 insertions(+), 36 deletions(-) create mode 100644 Oqtane.Server/Databases/LocalDbDatabase.cs create mode 100644 Oqtane.Server/Databases/SqlServerDatabase.cs create mode 100644 Oqtane.Server/Databases/SqliteDatabase.cs create mode 100644 Oqtane.Shared/Interfaces/IDatabase.cs create mode 100644 Oqtane.Shared/Models/Database.cs diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 558b8290..ff57e0bf 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; +using Oqtane.Interfaces; using Oqtane.Modules; using Oqtane.Providers; using Oqtane.Services; @@ -74,8 +75,8 @@ namespace Oqtane.Client var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); foreach (var assembly in assemblies) { - // dynamically register module services - var implementationTypes = assembly.GetInterfaces(); + // dynamically register module services + var implementationTypes = assembly.GetInterfaces(); foreach (var implementationType in implementationTypes) { if (implementationType.AssemblyQualifiedName != null) @@ -85,6 +86,17 @@ namespace Oqtane.Client } } + // dynamically register database providers + var databaseTypes = assembly.GetInterfaces(); + foreach (var databaseType in databaseTypes) + { + if (databaseType.AssemblyQualifiedName != null) + { + var serviceType = Type.GetType("Oqtane.Interfaces.IDatabase, Oqtane.Shared"); + builder.Services.AddScoped(serviceType ?? databaseType, databaseType); + } + } + // register client startup services var startUps = assembly.GetInstances(); foreach (var startup in startUps) @@ -115,7 +127,7 @@ namespace Oqtane.Client private static async Task LoadClientAssemblies(HttpClient http) { - // get list of loaded assemblies on the client + // get list of loaded assemblies on the client var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList(); // get assemblies from server and load into client app domain diff --git a/Oqtane.Client/UI/Installer.razor b/Oqtane.Client/UI/Installer.razor index 5983c253..8f140012 100644 --- a/Oqtane.Client/UI/Installer.razor +++ b/Oqtane.Client/UI/Installer.razor @@ -1,3 +1,4 @@ +@using Oqtane.Interfaces @namespace Oqtane.UI @inject NavigationManager NavigationManager @inject IInstallationService InstallationService @@ -5,6 +6,7 @@ @inject IUserService UserService @inject IJSRuntime JSRuntime @inject IStringLocalizer Localizer +@inject IEnumerable Databases
@@ -25,9 +27,12 @@ @@ -148,8 +153,6 @@ private string _hostEmail = string.Empty; private string _message = string.Empty; private string _integratedSecurityDisplay = "display: none;"; - private string _fileFieldsDisplay = "display: none;"; - private string _serverFieldsDisplay = "display: none;"; private string _loadingDisplay = "display: none;"; protected override async Task OnAfterRenderAsync(bool firstRender) @@ -176,10 +179,12 @@ StateHasChanged(); var connectionstring = ""; + var fullyQualifiedType = ""; switch (_databaseType) { case "LocalDB": connectionstring = "Data Source=" + _serverName + ";AttachDbFilename=|DataDirectory|\\" + _databaseName + ".mdf;Initial Catalog=" + _databaseName + ";Integrated Security=SSPI;"; + fullyQualifiedType = "Oqtane.Repository.Databases.LocalDbDatabase, Oqtane.Server"; break; case "SQLServer": connectionstring = "Data Source=" + _serverName + ";Initial Catalog=" + _databaseName + ";"; @@ -191,9 +196,11 @@ { connectionstring += "User ID=" + _username + ";Password=" + _password; } + fullyQualifiedType = "Oqtane.Repository.Databases.SqlServerDatabase, Oqtane.Server"; break; case "Sqlite": connectionstring = "Data Source=" + _fileName; + fullyQualifiedType = "Oqtane.Repository.Databases.SqliteDatabase, Oqtane.Server"; break; } @@ -201,7 +208,7 @@ var config = new InstallConfig { - DatabaseType = _databaseType, + DatabaseType = fullyQualifiedType, ConnectionString = connectionstring, Aliases = uri.Authority, HostEmail = _hostEmail, @@ -211,6 +218,8 @@ IsNewTenant = true, SiteName = Constants.DefaultSite }; + + var installation = await InstallationService.Install(config); if (installation.Success) diff --git a/Oqtane.Server/Databases/LocalDbDatabase.cs b/Oqtane.Server/Databases/LocalDbDatabase.cs new file mode 100644 index 00000000..0100c208 --- /dev/null +++ b/Oqtane.Server/Databases/LocalDbDatabase.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Oqtane.Interfaces; + +namespace Oqtane.Repository.Databases +{ + public class LocalDbDatabase : IDatabase + { + public string FriendlyName => "Local Database"; + public string Name => "LocalDB"; + + public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseSqlServer(connectionString); + } + } +} diff --git a/Oqtane.Server/Databases/SqlServerDatabase.cs b/Oqtane.Server/Databases/SqlServerDatabase.cs new file mode 100644 index 00000000..5b33d3cd --- /dev/null +++ b/Oqtane.Server/Databases/SqlServerDatabase.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Oqtane.Interfaces; + +namespace Oqtane.Repository.Databases +{ + public class SqlServerDatabase : IDatabase + { + public string FriendlyName => "SQL Server"; + + public string Name => "SqlServer"; + + public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseSqlServer(connectionString); + } + } +} diff --git a/Oqtane.Server/Databases/SqliteDatabase.cs b/Oqtane.Server/Databases/SqliteDatabase.cs new file mode 100644 index 00000000..4ac9a756 --- /dev/null +++ b/Oqtane.Server/Databases/SqliteDatabase.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Oqtane.Interfaces; + +namespace Oqtane.Repository.Databases +{ + public class SqliteDatabase : IDatabase + { + public string FriendlyName => Name; + + public string Name => "Sqlite"; + + public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseSqlite(connectionString); + } + } +} diff --git a/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs b/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs index 485423a4..e9c53a1b 100644 --- a/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs @@ -1,5 +1,10 @@ +using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Interfaces; +// ReSharper disable ConvertToUsingDeclaration namespace Oqtane.Extensions { @@ -7,16 +12,10 @@ namespace Oqtane.Extensions { public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string databaseType, string connectionString) { - switch (databaseType) - { - case "SqlServer": - optionsBuilder.UseSqlServer(connectionString); + var type = Type.GetType(databaseType); + var database = Activator.CreateInstance(type) as IDatabase; - break; - case "Sqlite": - optionsBuilder.UseSqlite(connectionString); - break; - } + database.UseDatabase(optionsBuilder, connectionString); return optionsBuilder; } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 36c41601..bf1064b3 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Hosting; using Oqtane.Infrastructure; +using Oqtane.Interfaces; using Oqtane.Modules; using Oqtane.Services; using Oqtane.Shared; @@ -46,6 +47,17 @@ namespace Microsoft.Extensions.DependencyInjection } } + // dynamically register database providers + var databaseTypes = assembly.GetInterfaces(); + foreach (var databaseType in databaseTypes) + { + if (databaseType.AssemblyQualifiedName != null) + { + var serviceType = Type.GetType("Oqtane.Interfaces.IDatabase, Oqtane.Shared"); + services.AddScoped(serviceType ?? databaseType, databaseType); + } + } + // dynamically register hosted services var serviceTypes = assembly.GetTypes(hostedServiceType); foreach (var serviceType in serviceTypes) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 1d2a2799..f91da21d 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -234,18 +234,18 @@ namespace Oqtane.Infrastructure if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases)) { - using (var db = new InstallationContext(install.DatabaseType, NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) + using (var db = GetInstallationContext()) { Tenant tenant; if (install.IsNewTenant) { tenant = new Tenant { Name = install.TenantName, - DBConnectionString = DenormalizeConnectionString(install.ConnectionString), - DBType = install.DatabaseType, - CreatedBy = "", - CreatedOn = DateTime.UtcNow, - ModifiedBy = "", - ModifiedOn = DateTime.UtcNow }; + DBConnectionString = DenormalizeConnectionString(install.ConnectionString), + DBType = install.DatabaseType, + CreatedBy = "", + CreatedOn = DateTime.UtcNow, + ModifiedBy = "", + ModifiedOn = DateTime.UtcNow }; db.Tenant.Add(tenant); db.SaveChanges(); _cache.Remove("tenants"); @@ -274,6 +274,14 @@ namespace Oqtane.Infrastructure return result; } + private InstallationContext GetInstallationContext() + { + var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; + var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)); + + return new InstallationContext(databaseType, connectionString); + } + private Installation MigrateTenants(InstallConfig install) { var result = new Installation { Success = false, Message = string.Empty }; @@ -284,9 +292,7 @@ namespace Oqtane.Infrastructure { var upgrades = scope.ServiceProvider.GetRequiredService(); - var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; - var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)); - using (var db = new InstallationContext(databaseType, connectionString)) + using (var db = GetInstallationContext()) { foreach (var tenant in db.Tenant.ToList()) { @@ -347,9 +353,8 @@ namespace Oqtane.Infrastructure if (moduleType != null) { var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; - var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)); - using (var db = new InstallationContext(databaseType, connectionString)) { + using (var db = GetInstallationContext()) + { foreach (var tenant in db.Tenant.ToList()) { var index = Array.FindIndex(versions, item => item == moduleDefinition.Version); diff --git a/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs b/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs index bfea92bd..9d76ada3 100644 --- a/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs +++ b/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs @@ -19,7 +19,7 @@ namespace Oqtane.Migrations migrationBuilder.Sql( @" UPDATE Tenant - SET DBType = 'SqlServer' + SET DBType = 'Oqtane.Repository.Databases.SqlServerDatabase, Oqtane.Server' "); } diff --git a/Oqtane.Server/Migrations/Extensions/ColumnsBuilderExtensions.cs b/Oqtane.Server/Migrations/Extensions/ColumnsBuilderExtensions.cs index 5c9a3a4e..ecd272db 100644 --- a/Oqtane.Server/Migrations/Extensions/ColumnsBuilderExtensions.cs +++ b/Oqtane.Server/Migrations/Extensions/ColumnsBuilderExtensions.cs @@ -9,8 +9,9 @@ namespace Oqtane.Migrations.Extensions public static OperationBuilder AddAutoIncrementColumn(this ColumnsBuilder table, string name) { return table.Column(name: name, nullable: false) - .Annotation("SqlServer:Identity", "1, 1") - .Annotation("Sqlite:Autoincrement", true); + .Annotation("SqlServer:Identity", "1, 1") + .Annotation("Sqlite:Autoincrement", true) + .Annotation("MySql:ValueGeneratedOnAdd", true); } public static OperationBuilder AddBooleanColumn(this ColumnsBuilder table, string name, bool nullable = false) diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index 550665da..5ee8b6c1 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -1,12 +1,10 @@ using System; -using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Oqtane.Extensions; -using Oqtane.Models; using Oqtane.Shared; // ReSharper disable BuiltInTypeReferenceStyleForMemberAccess diff --git a/Oqtane.Shared/Interfaces/IDatabase.cs b/Oqtane.Shared/Interfaces/IDatabase.cs new file mode 100644 index 00000000..8a03d74b --- /dev/null +++ b/Oqtane.Shared/Interfaces/IDatabase.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace Oqtane.Interfaces +{ + public interface IDatabase + { + public string FriendlyName { get; } + + public string Name { get; } + + public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString); + } +} diff --git a/Oqtane.Shared/Models/Database.cs b/Oqtane.Shared/Models/Database.cs new file mode 100644 index 00000000..e1855242 --- /dev/null +++ b/Oqtane.Shared/Models/Database.cs @@ -0,0 +1,9 @@ +namespace Oqtane.Models +{ + public class Database + { + public string Name { get; set; } + + public string Type { get; set; } + } +} diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 0f973a79..44aaab47 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -18,6 +18,7 @@ +