Updated the Installation of Oqtane to use Migrations

This commit is contained in:
Charles Nurse
2021-03-21 14:52:45 -07:00
parent 83e5502111
commit 8f1c760e87
27 changed files with 390 additions and 232 deletions

View File

@ -6,11 +6,8 @@ using Oqtane.Shared;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Enums; using Oqtane.Enums;
using System.Data.SqlClient;
using System.Data;
using System.Dynamic;
using Newtonsoft.Json;
using System; using System;
using Microsoft.Data.SqlClient;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {

Binary file not shown.

View File

@ -1,10 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using DbUp;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
@ -18,6 +15,11 @@ using Oqtane.Shared;
using Oqtane.Enums; using Oqtane.Enums;
using File = System.IO.File; using File = System.IO.File;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable ConvertToUsingDeclaration
// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess
// ReSharper disable UseIndexFromEndExpression
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
public class DatabaseManager : IDatabaseManager public class DatabaseManager : IDatabaseManager
@ -166,7 +168,7 @@ namespace Oqtane.Infrastructure
{ {
//create data directory if does not exist //create data directory if does not exist
var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory); if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory ?? String.Empty);
var connectionString = NormalizeConnectionString(install.ConnectionString); var connectionString = NormalizeConnectionString(install.ConnectionString);
using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(connectionString).Options)) using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(connectionString).Options))
@ -195,26 +197,20 @@ namespace Oqtane.Infrastructure
if (install.TenantName == TenantNames.Master) if (install.TenantName == TenantNames.Master)
{ {
MigrateScriptNamingConvention("Master", install.ConnectionString); try
var upgradeConfig = DeployChanges
.To
.SqlDatabase(NormalizeConnectionString(install.ConnectionString))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master.") && s.EndsWith(".sql",StringComparison.OrdinalIgnoreCase));
var upgrade = upgradeConfig.Build();
if (upgrade.IsUpgradeRequired())
{ {
var upgradeResult = upgrade.PerformUpgrade(); var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString};
result.Success = upgradeResult.Successful;
if (!result.Success) using (var masterDbContext = new MasterDBContext(new DbContextOptions<MasterDBContext>(), dbConfig))
{ {
result.Message = upgradeResult.Error.Message; // Push latest model into database
masterDbContext.Database.Migrate();
result.Success = true;
} }
} }
else catch (Exception ex)
{ {
result.Success = true; result.Message = ex.Message;
} }
if (result.Success) if (result.Success)
@ -251,10 +247,14 @@ namespace Oqtane.Infrastructure
tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName);
} }
foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (var aliasName in install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
var alias = new Alias { Name = aliasname, TenantId = tenant.TenantId, SiteId = -1, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow }; if (tenant != null)
db.Alias.Add(alias); {
var alias = new Alias { Name = aliasName, TenantId = tenant.TenantId, SiteId = -1, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
db.Alias.Add(alias);
}
db.SaveChanges(); db.SaveChanges();
} }
_cache.Remove("aliases"); _cache.Remove("aliases");
@ -270,7 +270,7 @@ namespace Oqtane.Infrastructure
{ {
var result = new Installation { Success = false, Message = string.Empty }; var result = new Installation { Success = false, Message = string.Empty };
string[] versions = Constants.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var versions = Constants.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
@ -280,29 +280,28 @@ namespace Oqtane.Infrastructure
{ {
foreach (var tenant in db.Tenant.ToList()) foreach (var tenant in db.Tenant.ToList())
{ {
MigrateScriptNamingConvention("Tenant", tenant.DBConnectionString); try
var upgradeConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(tenant.DBConnectionString))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant.") && s.EndsWith(".sql", StringComparison.OrdinalIgnoreCase));
var upgrade = upgradeConfig.Build();
if (upgrade.IsUpgradeRequired())
{ {
var upgradeResult = upgrade.PerformUpgrade(); var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString};
result.Success = upgradeResult.Successful; using (var tenantDbContext = new TenantDBContext(dbConfig, null))
if (!result.Success)
{ {
result.Message = upgradeResult.Error.Message; // Push latest model into database
tenantDbContext.Database.Migrate();
result.Success = true;
} }
} }
catch (Exception ex)
{
result.Message = ex.Message;
}
// execute any version specific upgrade logic // execute any version specific upgrade logic
string version = tenant.Version; var version = tenant.Version;
int index = Array.FindIndex(versions, item => item == version); var index = Array.FindIndex(versions, item => item == version);
if (index != (versions.Length - 1)) if (index != (versions.Length - 1))
{ {
if (index == -1) index = 0; if (index == -1) index = 0;
for (int i = index; i < versions.Length; i++) for (var i = index; i < versions.Length; i++)
{ {
upgrades.Upgrade(tenant, versions[i]); upgrades.Upgrade(tenant, versions[i]);
} }
@ -328,53 +327,61 @@ namespace Oqtane.Infrastructure
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
var moduledefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>(); var moduleDefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>();
var sql = scope.ServiceProvider.GetRequiredService<ISqlRepository>(); var sql = scope.ServiceProvider.GetRequiredService<ISqlRepository>();
foreach (var moduledefinition in moduledefinitions.GetModuleDefinitions()) foreach (var moduleDefinition in moduleDefinitions.GetModuleDefinitions())
{ {
if (!string.IsNullOrEmpty(moduledefinition.ReleaseVersions) && !string.IsNullOrEmpty(moduledefinition.ServerManagerType)) if (!string.IsNullOrEmpty(moduleDefinition.ReleaseVersions) && !string.IsNullOrEmpty(moduleDefinition.ServerManagerType))
{ {
Type moduletype = Type.GetType(moduledefinition.ServerManagerType); var moduleType = Type.GetType(moduleDefinition.ServerManagerType);
if (moduletype != null) if (moduleType != null)
{ {
string[] versions = moduledefinition.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{ {
foreach (var tenant in db.Tenant.ToList()) foreach (var tenant in db.Tenant.ToList())
{ {
int index = Array.FindIndex(versions, item => item == moduledefinition.Version); if (moduleType.GetInterface("IMigratable") != null)
if (tenant.Name == install.TenantName && install.TenantName != TenantNames.Master)
{ {
index = -1; var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IMigratable;
moduleObject.Migrate(tenant, MigrationType.Up);
} }
if (index != (versions.Length - 1)) else
{ {
if (index == -1) index = 0; var index = Array.FindIndex(versions, item => item == moduleDefinition.Version);
for (int i = index; i < versions.Length; i++) if (tenant.Name == install.TenantName && install.TenantName != TenantNames.Master)
{ {
try index = -1;
}
if (index != (versions.Length - 1))
{
if (index == -1) index = 0;
for (var i = index; i < versions.Length; i++)
{ {
if (moduletype.GetInterface("IInstallable") != null) try
{ {
var moduleobject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduletype); if (moduleType.GetInterface("IInstallable") != null)
((IInstallable)moduleobject).Install(tenant, versions[i]); {
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType);
((IInstallable)moduleObject).Install(tenant, versions[i]);
}
else
{
sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql");
}
} }
else catch (Exception ex)
{ {
sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"); result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.Message;
} }
} }
catch (Exception ex)
{
result.Message = "An Error Occurred Installing " + moduledefinition.Name + " Version " + versions[i] + " - " + ex.Message.ToString();
}
} }
} }
} }
if (string.IsNullOrEmpty(result.Message) && moduledefinition.Version != versions[versions.Length - 1]) if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1])
{ {
moduledefinition.Version = versions[versions.Length - 1]; moduleDefinition.Version = versions[versions.Length - 1];
db.Entry(moduledefinition).State = EntityState.Modified; db.Entry(moduleDefinition).State = EntityState.Modified;
db.SaveChanges(); db.SaveChanges();
} }
} }
@ -401,8 +408,8 @@ namespace Oqtane.Infrastructure
{ {
// use the SiteState to set the Alias explicitly so the tenant can be resolved // use the SiteState to set the Alias explicitly so the tenant can be resolved
var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>(); var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>();
string firstalias = install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; var firstAlias = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstalias); var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias);
var siteState = scope.ServiceProvider.GetRequiredService<SiteState>(); var siteState = scope.ServiceProvider.GetRequiredService<SiteState>();
siteState.Alias = alias; siteState.Alias = alias;
@ -413,82 +420,88 @@ namespace Oqtane.Infrastructure
var tenants = scope.ServiceProvider.GetRequiredService<ITenantRepository>(); var tenants = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
var users = scope.ServiceProvider.GetRequiredService<IUserRepository>(); var users = scope.ServiceProvider.GetRequiredService<IUserRepository>();
var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>(); var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
var userroles = scope.ServiceProvider.GetRequiredService<IUserRoleRepository>(); var userRoles = scope.ServiceProvider.GetRequiredService<IUserRoleRepository>();
var folders = scope.ServiceProvider.GetRequiredService<IFolderRepository>(); var folders = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
var log = scope.ServiceProvider.GetRequiredService<ILogManager>(); var log = scope.ServiceProvider.GetRequiredService<ILogManager>();
var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName); var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName);
site = new Site if (tenant != null)
{ {
TenantId = tenant.TenantId, site = new Site
Name = install.SiteName,
LogoFileId = null,
DefaultThemeType = install.DefaultTheme,
DefaultLayoutType = install.DefaultLayout,
DefaultContainerType = install.DefaultContainer,
SiteTemplateType = install.SiteTemplate
};
site = sites.AddSite(site);
IdentityUser identityUser = identityUserManager.FindByNameAsync(UserNames.Host).GetAwaiter().GetResult();
if (identityUser == null)
{
identityUser = new IdentityUser { UserName = UserNames.Host, Email = install.HostEmail, EmailConfirmed = true };
var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult();
if (create.Succeeded)
{ {
var user = new User TenantId = tenant.TenantId,
{ Name = install.SiteName,
SiteId = site.SiteId, LogoFileId = null,
Username = UserNames.Host, DefaultThemeType = install.DefaultTheme,
Password = install.HostPassword, DefaultLayoutType = install.DefaultLayout,
Email = install.HostEmail, DefaultContainerType = install.DefaultContainer,
DisplayName = install.HostName, SiteTemplateType = install.SiteTemplate
LastIPAddress = "", };
LastLoginOn = null site = sites.AddSite(site);
};
user = users.AddUser(user); var identityUser = identityUserManager.FindByNameAsync(UserNames.Host).GetAwaiter().GetResult();
var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0; if (identityUser == null)
var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null }; {
userroles.AddUserRole(userRole); identityUser = new IdentityUser {UserName = UserNames.Host, Email = install.HostEmail, EmailConfirmed = true};
var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult();
// add user folder if (create.Succeeded)
var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString()));
if (folder != null)
{ {
folders.AddFolder(new Folder var user = new User
{ {
SiteId = folder.SiteId, SiteId = site.SiteId,
ParentId = folder.FolderId, Username = UserNames.Host,
Name = "My Folder", Password = install.HostPassword,
Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()), Email = install.HostEmail,
Order = 1, DisplayName = install.HostName,
IsSystem = true, LastIPAddress = "",
Permissions = new List<Permission> LastLoginOn = null
};
user = users.AddUser(user);
var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0;
var userRole = new UserRole {UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null};
userRoles.AddUserRole(userRole);
// add user folder
var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString()));
if (folder != null)
{
folders.AddFolder(new Folder
{ {
new Permission(PermissionNames.Browse, user.UserId, true), SiteId = folder.SiteId,
new Permission(PermissionNames.View, RoleNames.Everyone, true), ParentId = folder.FolderId,
new Permission(PermissionNames.Edit, user.UserId, true), Name = "My Folder",
}.EncodePermissions(), Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()),
}); Order = 1,
IsSystem = true,
Permissions = new List<Permission>
{
new Permission(PermissionNames.Browse, user.UserId, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, user.UserId, true),
}.EncodePermissions(),
});
}
} }
} }
foreach (var aliasName in install.Aliases.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName);
if (alias != null)
{
alias.SiteId = site.SiteId;
aliases.UpdateAlias(alias);
}
}
tenant.Version = Constants.Version;
tenants.UpdateTenant(tenant);
} }
foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) if (site != null) log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site);
{
alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasname);
alias.SiteId = site.SiteId;
aliases.UpdateAlias(alias);
}
tenant.Version = Constants.Version;
tenants.UpdateTenant(tenant);
log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site);
} }
} }
} }
@ -508,7 +521,7 @@ namespace Oqtane.Infrastructure
private string DenormalizeConnectionString(string connectionString) private string DenormalizeConnectionString(string connectionString)
{ {
var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
connectionString = connectionString.Replace(dataDirectory, "|DataDirectory|"); connectionString = connectionString.Replace(dataDirectory ?? String.Empty, "|DataDirectory|");
return connectionString; return connectionString;
} }
@ -567,18 +580,5 @@ namespace Oqtane.Infrastructure
if (string.IsNullOrEmpty(value)) value = defaultValue; if (string.IsNullOrEmpty(value)) value = defaultValue;
return value; return value;
} }
private void MigrateScriptNamingConvention(string scriptType, string connectionString)
{
// migrate to new naming convention for scripts
var migrateConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(connectionString))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s == scriptType + ".00.00.00.00.sql");
var migrate = migrateConfig.Build();
if (migrate.IsUpgradeRequired())
{
migrate.PerformUpgrade();
}
}
} }
} }

View File

@ -1,10 +1,13 @@
using Oqtane.Models; using Microsoft.EntityFrameworkCore;
using Oqtane.Enums;
using Oqtane.Models;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
public interface IInstallable public interface IInstallable
{ {
bool Install(Tenant tenant, string version); bool Install(Tenant tenant, string version);
bool Uninstall(Tenant tenant); bool Uninstall(Tenant tenant);
} }
} }

View File

@ -0,0 +1,10 @@
using Oqtane.Enums;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public interface IMigratable
{
bool Migrate(Tenant tenant, MigrationType migrationType);
}
}

View File

@ -1,40 +1,64 @@
using Oqtane.Infrastructure; using System;
using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Modules.HtmlText.Models; using Oqtane.Modules.HtmlText.Models;
using Oqtane.Modules.HtmlText.Repository; using Oqtane.Modules.HtmlText.Repository;
using System.Net; using System.Net;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Enums;
// ReSharper disable ConvertToUsingDeclaration
namespace Oqtane.Modules.HtmlText.Manager namespace Oqtane.Modules.HtmlText.Manager
{ {
public class HtmlTextManager : IInstallable, IPortable public class HtmlTextManager : IMigratable, IPortable
{ {
private IHtmlTextRepository _htmlTexts; private readonly IHtmlTextRepository _htmlText;
private ISqlRepository _sql; private readonly ISqlRepository _sql;
public HtmlTextManager(IHtmlTextRepository htmltexts, ISqlRepository sql) public HtmlTextManager(IHtmlTextRepository htmlText, ISqlRepository sql)
{ {
_htmlTexts = htmltexts; _htmlText = htmlText;
_sql = sql; _sql = sql;
} }
public bool Install(Tenant tenant, string version) public bool Migrate(Tenant tenant, MigrationType migrationType)
{ {
return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText." + version + ".sql"); var result = true;
}
public bool Uninstall(Tenant tenant) var dbConfig = new DbConfig(null, null) {ConnectionString = tenant.DBConnectionString};
{ using (var db = new HtmlTextContext(dbConfig, null))
return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText.Uninstall.sql"); {
try
{
var migrator = db.GetService<IMigrator>();
if (migrationType == MigrationType.Down)
{
migrator.Migrate(Migration.InitialDatabase);
}
else
{
migrator.Migrate();
}
}
catch (Exception e)
{
Console.WriteLine(e);
result = false;
}
}
return result;
} }
public string ExportModule(Module module) public string ExportModule(Module module)
{ {
string content = ""; string content = "";
HtmlTextInfo htmltext = _htmlTexts.GetHtmlText(module.ModuleId); var htmlText = _htmlText.GetHtmlText(module.ModuleId);
if (htmltext != null) if (htmlText != null)
{ {
content = WebUtility.HtmlEncode(htmltext.Content); content = WebUtility.HtmlEncode(htmlText.Content);
} }
return content; return content;
} }
@ -42,18 +66,18 @@ namespace Oqtane.Modules.HtmlText.Manager
public void ImportModule(Module module, string content, string version) public void ImportModule(Module module, string content, string version)
{ {
content = WebUtility.HtmlDecode(content); content = WebUtility.HtmlDecode(content);
HtmlTextInfo htmltext = _htmlTexts.GetHtmlText(module.ModuleId); var htmlText = _htmlText.GetHtmlText(module.ModuleId);
if (htmltext != null) if (htmlText != null)
{ {
htmltext.Content = content; htmlText.Content = content;
_htmlTexts.UpdateHtmlText(htmltext); _htmlText.UpdateHtmlText(htmlText);
} }
else else
{ {
htmltext = new HtmlTextInfo(); htmlText = new HtmlTextInfo();
htmltext.ModuleId = module.ModuleId; htmlText.ModuleId = module.ModuleId;
htmltext.Content = content; htmlText.Content = content;
_htmlTexts.AddHtmlText(htmltext); _htmlText.AddHtmlText(htmlText);
} }
} }
} }

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Modules.HtmlText.Migrations.EntityBuilders;
using Oqtane.Modules.HtmlText.Repository;
namespace Oqtane.Modules.HtmlText.Migrations
{
[DbContext(typeof(HtmlTextContext))]
[Migration("HtmlText.01.00.00.00")]
public class InitializeModule : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
//Create HtmlText table
var entityBuilder = new HtmlTextEntityBuilder(migrationBuilder);
entityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
//Drop HtmlText table
var entityBuilder = new HtmlTextEntityBuilder(migrationBuilder);
entityBuilder.Drop();
}
}
}

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Migrations;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Migrations.Extensions;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Modules.HtmlText.Migrations.EntityBuilders
{
public class HtmlTextEntityBuilder : AuditableBaseEntityBuilder<HtmlTextEntityBuilder>
{
private const string _entityTableName = "HtmlText";
private readonly PrimaryKey<HtmlTextEntityBuilder> _primaryKey = new("PK_HtmlText", x => x.HtmlTextId);
private readonly ForeignKey<HtmlTextEntityBuilder> _moduleForeignKey = new("FK_HtmlText_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
public HtmlTextEntityBuilder(MigrationBuilder migrationBuilder) : base(migrationBuilder)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_moduleForeignKey);
}
protected override HtmlTextEntityBuilder BuildTable(ColumnsBuilder table)
{
HtmlTextId = table.AddAutoIncrementColumn("HtmlTextId");
ModuleId = table.AddIntegerColumn("ModuleId");
Content = table.AddMaxStringColumn("Content");
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> HtmlTextId { get; set; }
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
public OperationBuilder<AddColumnOperation> Content { get; set; }
}
}

View File

@ -10,7 +10,7 @@ namespace Oqtane.Modules.HtmlText.Repository
{ {
public virtual DbSet<HtmlTextInfo> HtmlText { get; set; } public virtual DbSet<HtmlTextInfo> HtmlText { get; set; }
public HtmlTextContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor, IConfiguration configuration) : base(tenantResolver, accessor, configuration) public HtmlTextContext(IDbConfig dbConfig, ITenantResolver tenantResolver) : base(dbConfig, tenantResolver)
{ {
// ContextBase handles multi-tenant database connections // ContextBase handles multi-tenant database connections
} }

View File

@ -1,19 +0,0 @@
CREATE TABLE [dbo].[HtmlText](
[HtmlTextId] [int] IDENTITY(1,1) NOT NULL,
[ModuleId] [int] NOT NULL,
[Content] [nvarchar](max) NOT NULL,
[CreatedBy] [nvarchar](256) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[ModifiedBy] [nvarchar](256) NOT NULL,
[ModifiedOn] [datetime] NOT NULL,
CONSTRAINT [PK_HtmlText] PRIMARY KEY CLUSTERED
(
[HtmlTextId] ASC
)
)
GO
ALTER TABLE [dbo].[HtmlText] WITH CHECK ADD CONSTRAINT [FK_HtmlText_Module] FOREIGN KEY([ModuleId])
REFERENCES [dbo].[Module] ([ModuleId])
ON DELETE CASCADE
GO

View File

@ -1,2 +0,0 @@
DROP TABLE [dbo].[HtmlText]
GO

View File

@ -20,6 +20,9 @@
<Compile Remove="wwwroot\Modules\Templates\**" /> <Compile Remove="wwwroot\Modules\Templates\**" />
<Content Remove="wwwroot\Modules\Templates\**" /> <Content Remove="wwwroot\Modules\Templates\**" />
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" /> <EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
<Compile Remove="bin\**" />
<EmbeddedResource Remove="bin\**" />
<Content Remove="bin\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Scripts\Master.00.00.00.00.sql" /> <EmbeddedResource Include="Scripts\Master.00.00.00.00.sql" />
@ -36,14 +39,12 @@
<EmbeddedResource Include="Scripts\Tenant.02.00.01.01.sql" /> <EmbeddedResource Include="Scripts\Tenant.02.00.01.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.01.02.sql" /> <EmbeddedResource Include="Scripts\Tenant.02.00.01.02.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.01.03.sql" /> <EmbeddedResource Include="Scripts\Tenant.02.00.01.03.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="dbup" Version="4.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.4" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.4"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -67,6 +68,9 @@
<UpgradeFiles Include="$(ProjectDir)bin\Release\net5.0\Oqtane.Upgrade.runtimeconfig.json" /> <UpgradeFiles Include="$(ProjectDir)bin\Release\net5.0\Oqtane.Upgrade.runtimeconfig.json" />
<TemplateFiles Include="$(ProjectDir)wwwroot\Modules\Templates\**\*.*" /> <TemplateFiles Include="$(ProjectDir)wwwroot\Modules\Templates\**\*.*" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="bin\**" />
</ItemGroup>
<Target Name="AddPayloadsFolder" AfterTargets="Publish"> <Target Name="AddPayloadsFolder" AfterTargets="Publish">
<Copy SourceFiles="@(UpgradeFiles)" DestinationFiles="@(UpgradeFiles->'$(PublishDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" /> <Copy SourceFiles="@(UpgradeFiles)" DestinationFiles="@(UpgradeFiles->'$(PublishDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
<Copy SourceFiles="@(TemplateFiles)" DestinationFiles="@(TemplateFiles->'$(PublishDir)wwwroot\Modules\Templates\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" /> <Copy SourceFiles="@(TemplateFiles)" DestinationFiles="@(TemplateFiles->'$(PublishDir)wwwroot\Modules\Templates\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />

View File

@ -7,48 +7,56 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Models; using Oqtane.Models;
// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess
namespace Oqtane.Repository namespace Oqtane.Repository
{ {
public class DBContextBase : IdentityUserContext<IdentityUser> public class DBContextBase : IdentityUserContext<IdentityUser>
{ {
private ITenantResolver _tenantResolver; private readonly IDbConfig _dbConfig;
private IHttpContextAccessor _accessor; private readonly ITenantResolver _tenantResolver;
private readonly IConfiguration _configuration;
public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor accessor, IConfiguration configuration) public DBContextBase(IDbConfig dbConfig, ITenantResolver tenantResolver)
{ {
_dbConfig = dbConfig;
_tenantResolver = tenantResolver; _tenantResolver = tenantResolver;
_accessor = accessor;
_configuration = configuration;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
var tenant = _tenantResolver.GetTenant(); var connectionString = _dbConfig.ConnectionString;
if (tenant != null)
if (string.IsNullOrEmpty(connectionString) && _tenantResolver != null)
{
var tenant = _tenantResolver.GetTenant();
var configuration = _dbConfig.Configuration;
if (tenant != null)
{
connectionString = tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
}
else
{
if (!String.IsNullOrEmpty(configuration.GetConnectionString("DefaultConnection")))
{
connectionString = configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
}
}
}
if (!string.IsNullOrEmpty(connectionString))
{ {
var connectionString = tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
optionsBuilder.UseOqtaneDatabase(connectionString); optionsBuilder.UseOqtaneDatabase(connectionString);
} }
else
{
if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
var connectionString = _configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
optionsBuilder.UseOqtaneDatabase(connectionString);
}
}
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
} }
public override int SaveChanges() public override int SaveChanges()
{ {
DbContextUtils.SaveChanges(this, _accessor); DbContextUtils.SaveChanges(this, _dbConfig.Accessor);
return base.SaveChanges(); return base.SaveChanges();
} }

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
namespace Oqtane.Repository
{
public class DbConfig : IDbConfig
{
public DbConfig(IHttpContextAccessor accessor, IConfiguration configuration)
{
Accessor = accessor;
Configuration = configuration;
}
public IHttpContextAccessor Accessor { get; }
public IConfiguration Configuration { get; }
public string ConnectionString { get; set; }
}
}

View File

@ -6,7 +6,7 @@ using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository
{ {
public class DbContextUtils public static class DbContextUtils
{ {
public static void SaveChanges(DbContext context, IHttpContextAccessor accessor) public static void SaveChanges(DbContext context, IHttpContextAccessor accessor)
{ {

View File

@ -22,5 +22,8 @@ namespace Oqtane.Repository
public virtual DbSet<Tenant> Tenant { get; set; } public virtual DbSet<Tenant> Tenant { get; set; }
public virtual DbSet<ModuleDefinition> ModuleDefinition { get; set; } public virtual DbSet<ModuleDefinition> ModuleDefinition { get; set; }
public virtual DbSet<Job> Job { get; set; } public virtual DbSet<Job> Job { get; set; }
public virtual DbSet<JobLog> JobLog { get; set; }
} }
} }

View File

@ -1,31 +1,42 @@
using System; using System;
using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Oqtane.Models; using Oqtane.Models;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Oqtane.Extensions; using Oqtane.Extensions;
// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable CheckNamespace
namespace Oqtane.Repository namespace Oqtane.Repository
{ {
public class MasterDBContext : DbContext public class MasterDBContext : DbContext
{ {
private readonly IHttpContextAccessor _accessor; private readonly IDbConfig _dbConfig;
private readonly IConfiguration _configuration;
public MasterDBContext(DbContextOptions<MasterDBContext> options, IHttpContextAccessor accessor, IConfiguration configuration) : base(options) public MasterDBContext(DbContextOptions<MasterDBContext> options, IDbConfig dbConfig) : base(options)
{ {
_accessor = accessor; _dbConfig = dbConfig;
_configuration = configuration;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) var connectionString = _dbConfig.ConnectionString;
{ var configuration = _dbConfig.Configuration;
var connectionString = _configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
if(string.IsNullOrEmpty(connectionString) && configuration != null)
{
if (!String.IsNullOrEmpty(configuration.GetConnectionString("DefaultConnection")))
{
connectionString = configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
}
}
if (!string.IsNullOrEmpty(connectionString))
{
optionsBuilder.UseOqtaneDatabase(connectionString); optionsBuilder.UseOqtaneDatabase(connectionString);
} }
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
@ -39,7 +50,7 @@ namespace Oqtane.Repository
public override int SaveChanges() public override int SaveChanges()
{ {
DbContextUtils.SaveChanges(this, _accessor); DbContextUtils.SaveChanges(this, _dbConfig.Accessor);
return base.SaveChanges(); return base.SaveChanges();
} }

View File

@ -24,7 +24,7 @@ namespace Oqtane.Repository
public virtual DbSet<Language> Language { get; set; } public virtual DbSet<Language> Language { get; set; }
public TenantDBContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor, IConfiguration configuration) : base(tenantResolver, accessor, configuration) public TenantDBContext(IDbConfig dbConfig, ITenantResolver tenantResolver) : base(dbConfig, tenantResolver)
{ {
// DBContextBase handles multi-tenant database connections // DBContextBase handles multi-tenant database connections
} }

View File

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
namespace Oqtane.Repository
{
public interface IDbConfig
{
public IHttpContextAccessor Accessor { get; }
public IConfiguration Configuration { get; }
public string ConnectionString { get; set; }
}
}

View File

@ -1,5 +1,5 @@
using System.Data.SqlClient; using System.Reflection;
using System.Reflection; using Microsoft.Data.SqlClient;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository

View File

@ -1,9 +1,9 @@
using System; using System;
using System.Data; using System.Data;
using System.Data.SqlClient;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.Data.SqlClient;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -59,8 +59,8 @@ namespace Oqtane.Repository
public int ExecuteNonQuery(Tenant tenant, string query) public int ExecuteNonQuery(Tenant tenant, string query)
{ {
SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString)); var conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString));
SqlCommand cmd = conn.CreateCommand(); var cmd = conn.CreateCommand();
using (conn) using (conn)
{ {
PrepareCommand(conn, cmd, query); PrepareCommand(conn, cmd, query);

View File

@ -130,6 +130,7 @@ namespace Oqtane
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IDbConfig, DbConfig>();
services.AddDbContext<MasterDBContext>(options => { }); services.AddDbContext<MasterDBContext>(options => { });
services.AddDbContext<TenantDBContext>(options => { }); services.AddDbContext<TenantDBContext>(options => { });
@ -211,7 +212,7 @@ namespace Oqtane
services.AddTransient<IUpgradeManager, UpgradeManager>(); services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>(); services.AddTransient<ILanguageRepository, LanguageRepository>();
// load the external assemblies into the app domain, install services // load the external assemblies into the app domain, install services
services.AddOqtane(_runtime, _supportedCultures); services.AddOqtane(_runtime, _supportedCultures);
services.AddMvc() services.AddMvc()

View File

@ -1,6 +1,10 @@
{ {
"Database": {
"DatabaseType": "",
"DatabaseEngineVersion": ""
},
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "" "DefaultConnection": "Data Source=.;Initial Catalog=Oqtane-Migrations;Integrated Security=SSPI;"
}, },
"Runtime": "Server", "Runtime": "Server",
"Installation": { "Installation": {

View File

@ -0,0 +1 @@
/* Module Custom Styles */

View File

@ -0,0 +1 @@
/* Module Script */

View File

@ -0,0 +1 @@
["\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.pdb","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.pdb","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.pdb","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.css","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.js"]

View File

@ -0,0 +1,8 @@
namespace Oqtane.Enums
{
public enum MigrationType
{
Up,
Down
}
}