Merge pull request #406 from sbwalker/master

Added IInstallable interface and uninstall implementation for modules. Refactoring module installation to use interface still in progress.
This commit is contained in:
Shaun Walker 2020-04-26 13:16:37 -04:00 committed by GitHub
commit 11790a0b64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 158 additions and 76 deletions

View File

@ -52,6 +52,7 @@
</td>
</tr>
</table>
<a class="btn btn-primary" href="swagger/index.html" target="_new">Access Framework API</a>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;

View File

@ -9,7 +9,8 @@ namespace Oqtane.Modules.HtmlText
Name = "HtmlText",
Description = "Renders HTML or Text Content",
Version = "1.0.0",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server"
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0"
};
}
}

View File

@ -12,7 +12,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using System;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Extensions.DependencyInjection;
// ReSharper disable StringIndexOfIsCultureSpecific.1
namespace Oqtane.Controllers
@ -25,21 +25,17 @@ namespace Oqtane.Controllers
private readonly IUserPermissions _userPermissions;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
private readonly ITenantResolver _resolver;
private readonly ITenantRepository _tenants;
private readonly ISqlRepository _sql;
private readonly IServiceProvider _serviceProvider;
private readonly ILogManager _logger;
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ITenantRepository tenants, ISqlRepository sql, ILogManager logger)
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ILogManager logger)
{
_moduleDefinitions = moduleDefinitions;
_modules = modules;
_userPermissions = userPermissions;
_installationManager = installationManager;
_environment = environment;
_resolver = resolver;
_tenants = tenants;
_sql = sql;
_serviceProvider = serviceProvider;
_logger = logger;
}
@ -100,57 +96,40 @@ namespace Oqtane.Controllers
[Authorize(Roles = Constants.HostRole)]
public void Delete(int id, int siteid)
{
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteid).ToList();
ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionId == id).FirstOrDefault();
ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, siteid);
if (moduledefinition != null)
{
// server assembly name should follow client naming convention
string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName).Replace(".Client",".Server");
string uninstallScript = "";
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(a => a.GetName().Name == assemblyname);
if (assembly != null)
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType))
{
Stream resourceStream = assembly.GetManifestResourceStream(assemblyname + ".Scripts.Uninstall.sql");
if (resourceStream != null)
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IInstallable") != null)
{
using (var reader = new StreamReader(resourceStream))
{
uninstallScript = reader.ReadToEnd();
}
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IInstallable)moduleobject).Uninstall();
}
}
foreach (Tenant tenant in _tenants.GetTenants())
{
// uninstall module database schema
if (!string.IsNullOrEmpty(uninstallScript))
{
_sql.ExecuteScript(tenant, uninstallScript);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Uninstall Script Executed For {AssemblyName}", assemblyname);
}
// clean up module schema versions
_sql.ExecuteNonQuery(tenant, "DELETE FROM [dbo].[SchemaVersions] WHERE ScriptName LIKE '" + assemblyname + "%'");
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Schema Versions Removed For {AssemblyName}", assemblyname);
}
// format root assembly name
assemblyname = assemblyname.Replace(".Server", "");
// clean up module static resource folder
string folder = Path.Combine(_environment.WebRootPath, "Modules\\" + assemblyname);
if (Directory.Exists(folder))
string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName);
if (assemblyname != "Oqtane.Client")
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {AssemblynName}", assemblyname);
}
assemblyname = assemblyname.Replace(".Client", "");
// remove module assembly from /bin
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*"))
{
System.IO.File.Delete(file);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly Removed {Filename}", file);
// clean up module static resource folder
string folder = Path.Combine(_environment.WebRootPath, "Modules\\" + assemblyname);
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {AssemblynName}", assemblyname);
}
// remove module assembly from /bin
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*"))
{
System.IO.File.Delete(file);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly Removed {Filename}", file);
}
}
// remove module definition
@ -247,12 +226,6 @@ namespace Oqtane.Controllers
text = text.Replace("[File]", Path.GetFileName(filePath));
text = text.Replace("[FrameworkVersion]", Constants.Version);
System.IO.File.WriteAllText(filePath, text);
if (Path.GetExtension(filePath).ToLower() == ".sql" && !filePath.ToLower().Contains("uninstall"))
{
// execute installation script in curent tenant
_sql.ExecuteScript(_resolver.GetTenant(), text);
}
}
DirectoryInfo[] folders = current.GetDirectories();

View File

@ -184,7 +184,7 @@ namespace Oqtane.Infrastructure
.SqlDatabase(connectionString)
.WithVariable("ConnectionString", connectionString)
.WithVariable("Alias", alias)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => master || !s.Contains("Master."));
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master."));
var dbUpgrade = dbUpgradeConfig.Build();
if (!dbUpgrade.IsUpgradeRequired())

View File

@ -0,0 +1,8 @@
namespace Oqtane.Infrastructure
{
public interface IInstallable
{
bool Install(string version);
bool Uninstall();
}
}

View File

@ -1,17 +1,31 @@
using Oqtane.Models;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Modules.HtmlText.Models;
using Oqtane.Modules.HtmlText.Repository;
using System.Net;
namespace Oqtane.Modules.HtmlText.Manager
{
public class HtmlTextManager : IPortable
public class HtmlTextManager : IInstallable, IPortable
{
private IHtmlTextRepository _htmlTexts;
private ISqlRepository _sql;
public HtmlTextManager(IHtmlTextRepository htmltexts)
public HtmlTextManager(IHtmlTextRepository htmltexts, ISqlRepository sql)
{
_htmlTexts = htmltexts;
_sql = sql;
}
public bool Install(string version)
{
return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText." + version + ".sql");
}
public bool Uninstall()
{
return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText.Uninstall.sql");
}
public string ExportModule(Module module)

View File

@ -0,0 +1,19 @@
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

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

View File

@ -18,6 +18,13 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<None Remove="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
<EmbeddedResource Include="Scripts\Master.00.00.00.sql" />
<EmbeddedResource Include="Scripts\Master.00.00.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.00.00.00.sql" />

View File

@ -1,10 +1,12 @@
using System.Data.SqlClient;
using System.Reflection;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface ISqlRepository
{
bool ExecuteEmbeddedScript(Assembly assembly, string script);
void ExecuteScript(Tenant tenant, string script);
int ExecuteNonQuery(Tenant tenant, string query);
SqlDataReader ExecuteReader(Tenant tenant, string query);

View File

@ -188,18 +188,15 @@ namespace Oqtane.Repository
{
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : ""),
Version = "1.0.0"
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1];
moduledefinition.AssemblyName = assembly.FullName.Split(",")[0];
if (assembly.FullName.StartsWith("Oqtane.Client"))
{
moduledefinition.Version = Constants.Version;
}
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -20,7 +19,6 @@ namespace Oqtane.Repository
private readonly IRoleRepository _roleRepository;
private readonly IProfileRepository _profileRepository;
private readonly IFolderRepository _folderRepository;
private readonly IFileRepository _fileRepository;
private readonly IPageRepository _pageRepository;
private readonly IModuleRepository _moduleRepository;
private readonly IPageModuleRepository _pageModuleRepository;
@ -30,15 +28,14 @@ namespace Oqtane.Repository
private readonly IConfigurationRoot _config;
public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IFileRepository fileRepository, IPageRepository pageRepository,
IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IPermissionRepository permissionRepository, IServiceProvider serviceProvider,
public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IPageRepository pageRepository,
IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IServiceProvider serviceProvider,
IConfigurationRoot config)
{
_db = context;
_roleRepository = roleRepository;
_profileRepository = profileRepository;
_folderRepository = folderRepository;
_fileRepository = fileRepository;
_pageRepository = pageRepository;
_moduleRepository = moduleRepository;
_pageModuleRepository = pageModuleRepository;
@ -787,7 +784,14 @@ namespace Oqtane.Repository
if (moduletype != null && moduletype.GetInterface("IPortable") != null)
{
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable) moduleobject).ImportModule(module, pagetemplatemodule.Content, moduledefinition.Version);
try
{
((IPortable)moduleobject).ImportModule(module, pagetemplatemodule.Content, moduledefinition.Version);
}
catch
{
// error in module import
}
}
}

View File

@ -1,12 +1,61 @@
using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class SqlRepository : ISqlRepository
{
private readonly ITenantRepository _tenants;
public SqlRepository(ITenantRepository tenants)
{
_tenants = tenants;
}
public bool ExecuteEmbeddedScript(Assembly assembly, string filename)
{
// script must be included as an Embedded Resource within an assembly
bool success = true;
string uninstallScript = "";
if (assembly != null)
{
string name = assembly.GetManifestResourceNames().FirstOrDefault(item => item.EndsWith("." + filename));
if (name != null)
{
Stream resourceStream = assembly.GetManifestResourceStream(name);
if (resourceStream != null)
{
using (var reader = new StreamReader(resourceStream))
{
uninstallScript = reader.ReadToEnd();
}
}
}
}
if (!string.IsNullOrEmpty(uninstallScript))
{
foreach (Tenant tenant in _tenants.GetTenants())
{
try
{
ExecuteScript(tenant, uninstallScript);
}
catch
{
success = false;
}
}
}
return success;
}
public void ExecuteScript(Tenant tenant, string script)
{

View File

@ -19,6 +19,7 @@ namespace Oqtane.Models
PermissionNames = "";
ServerManagerType = "";
ControlTypeRoutes = "";
ReleaseVersions = "";
Template = "";
}
@ -34,8 +35,7 @@ namespace Oqtane.Models
public string ModifiedBy { get; set; }
public DateTime ModifiedOn { get; set; }
[NotMapped]
public int SiteId { get; set; }
// additional IModule properties
[NotMapped]
public string Owner { get; set; }
[NotMapped]
@ -53,12 +53,18 @@ namespace Oqtane.Models
[NotMapped]
public string ControlTypeRoutes { get; set; }
[NotMapped]
public string Template { get; set; }
public string ReleaseVersions { get; set; }
// internal properties
[NotMapped]
public int SiteId { get; set; }
[NotMapped]
public string ControlTypeTemplate { get; set; }
[NotMapped]
public string AssemblyName { get; set; }
[NotMapped]
public string Permissions { get; set; }
[NotMapped]
public string Template { get; set; }
}
}