From 72b06b16cfcc835e6e22f7219b214f5b750b517c Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 24 May 2021 15:50:38 -0400 Subject: [PATCH] fix #1272 - add support for ref folder in package installation --- .../Modules/Admin/ModuleDefinitions/Add.razor | 2 +- .../Admin/ModuleDefinitions/Index.razor | 4 +- Oqtane.Client/Modules/Admin/Themes/Add.razor | 2 +- .../Modules/Admin/Themes/Index.razor | 4 +- .../Controllers/ModuleDefinitionController.cs | 22 +- .../Controllers/PackageController.cs | 2 +- Oqtane.Server/Controllers/ThemeController.cs | 27 +-- .../Infrastructure/DatabaseManager.cs | 3 +- .../Infrastructure/InstallationManager.cs | 224 ++++++++++-------- .../Interfaces/IInstallationManager.cs | 3 +- .../Repository/ModuleDefinitionRepository.cs | 9 +- Oqtane.Server/Startup.cs | 2 +- .../wwwroot/Packages/Oqtane.Blogs.log | 1 + .../Packages/Oqtane.Database.SqlServer.log | 1 + 14 files changed, 160 insertions(+), 146 deletions(-) create mode 100644 Oqtane.Server/wwwroot/Packages/Oqtane.Blogs.log create mode 100644 Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.log diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index 155a0c72..a1cd7e47 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -91,7 +91,7 @@ { try { - await PackageService.DownloadPackageAsync(packageid, version, "Modules"); + await PackageService.DownloadPackageAsync(packageid, version, "Packages"); await logger.LogInformation("Module {ModuleDefinitionName} {Version} Downloaded Successfully", packageid, version); AddModuleMessage(Localizer["Modules Downloaded Successfully. Click Install To Complete Installation."], MessageType.Success); StateHasChanged(); diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index 1ffc631b..c9c47456 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -85,7 +85,7 @@ else { try { - await PackageService.DownloadPackageAsync(packagename, version, "Modules"); + await PackageService.DownloadPackageAsync(packagename, version, "Packages"); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version); await ModuleDefinitionService.InstallModuleDefinitionsAsync(); AddModuleMessage(Localizer["Module Installed Successfully. You Must Restart Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success); @@ -103,7 +103,7 @@ else { await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); AddModuleMessage(Localizer["Module Deleted Successfully"], MessageType.Success); - StateHasChanged(); + NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); } catch (Exception ex) { diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 24501517..b0233be0 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -91,7 +91,7 @@ { try { - await PackageService.DownloadPackageAsync(packageid, version, "Themes"); + await PackageService.DownloadPackageAsync(packageid, version, "Packages"); await logger.LogInformation("Theme {ThemeName} {Version} Downloaded Successfully", packageid, version); AddModuleMessage(Localizer["Themes Downloaded Successfully. Click Install To Complete Installation."], MessageType.Success); StateHasChanged(); diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 60df31cb..fc13417f 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -86,7 +86,7 @@ else { try { - await PackageService.DownloadPackageAsync(packagename, version, "Themes"); + await PackageService.DownloadPackageAsync(packagename, version, "Packages"); await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version); await ThemeService.InstallThemesAsync(); AddModuleMessage(Localizer["Theme Installed Successfully. You Must Restart Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success); @@ -104,7 +104,7 @@ else { await ThemeService.DeleteThemeAsync(Theme.ThemeName); AddModuleMessage(Localizer["Theme Deleted Successfully"], MessageType.Success); - StateHasChanged(); + NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); } catch (Exception ex) { diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 30d73d66..b0420ca8 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -90,7 +90,7 @@ namespace Oqtane.Controllers public void InstallModules() { _logger.Log(LogLevel.Information, this, LogFunction.Create, "Modules Installed"); - _installationManager.InstallPackages("Modules"); + _installationManager.InstallPackages(); } // DELETE api//5?siteid=x @@ -128,25 +128,8 @@ namespace Oqtane.Controllers } // remove module assets - string assetpath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)); - if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json"))) + if (_installationManager.UninstallPackage(moduledefinition.PackageName)) { - // use assets.json to clean up file resources - List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json"))); - assets.Reverse(); - foreach(string asset in assets) - { - // legacy support for assets that were stored as absolute paths - string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; - if (System.IO.File.Exists(filepath)) - { - System.IO.File.Delete(filepath); - if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any()) - { - Directory.Delete(Path.GetDirectoryName(filepath)); - } - } - } _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); } else @@ -160,6 +143,7 @@ namespace Oqtane.Controllers } // clean up module static resource folder + string assetpath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)); if (Directory.Exists(assetpath)) { Directory.Delete(assetpath, true); diff --git a/Oqtane.Server/Controllers/PackageController.cs b/Oqtane.Server/Controllers/PackageController.cs index b61585b5..c5c94dc7 100644 --- a/Oqtane.Server/Controllers/PackageController.cs +++ b/Oqtane.Server/Controllers/PackageController.cs @@ -97,7 +97,7 @@ namespace Oqtane.Controllers public void InstallPackages() { _logger.Log(LogLevel.Information, this, LogFunction.Create, "Packages Installed"); - _installationManager.InstallPackages("Packages"); + _installationManager.InstallPackages(); } } diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index 5bf8a2f3..79e53e64 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -45,7 +45,7 @@ namespace Oqtane.Controllers public void InstallThemes() { _logger.Log(LogLevel.Information, this, LogFunction.Create, "Themes Installed"); - _installationManager.InstallPackages("Themes"); + _installationManager.InstallPackages(); } // DELETE api//xxx @@ -58,25 +58,8 @@ namespace Oqtane.Controllers if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client") { // remove theme assets - string assetpath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName)); - if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json"))) + if (_installationManager.UninstallPackage(theme.PackageName)) { - // use assets.json to clean up file resources - List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json"))); - assets.Reverse(); - foreach (string asset in assets) - { - // legacy support for assets that were stored as absolute paths - string filepath = (asset.StartsWith("\\")) ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; - if (System.IO.File.Exists(filepath)) - { - System.IO.File.Delete(filepath); - if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any()) - { - Directory.Delete(Path.GetDirectoryName(filepath)); - } - } - } _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Assets Removed For {ThemeName}", theme.ThemeName); } else @@ -90,10 +73,10 @@ namespace Oqtane.Controllers } // clean up theme static resource folder - string folder = Path.Combine(_environment.WebRootPath, "Themes" , Utilities.GetTypeName(theme.ThemeName)); - if (Directory.Exists(folder)) + string assetpath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName)); + if (Directory.Exists(assetpath)) { - Directory.Delete(folder, true); + Directory.Delete(assetpath, true); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Static Resource Folder Removed For {ThemeName}", theme.ThemeName); } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 7967d8c3..6a9ba2ba 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -17,7 +17,6 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; using Oqtane.Enums; -using Oqtane.Interfaces; using File = System.IO.File; // ReSharper disable MemberCanBePrivate.Global @@ -205,7 +204,7 @@ namespace Oqtane.Infrastructure using (var scope = _serviceScopeFactory.CreateScope()) { var installationManager = scope.ServiceProvider.GetRequiredService(); - installationManager.InstallPackages(packageFolderName); + installationManager.InstallPackages(); result.Success = true; } } diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index ef3b0b7e..2f43f308 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Linq; using System.Reflection; using System.Text.Json; using System.Xml; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Oqtane.Shared; // ReSharper disable AssignNullToNotNullAttribute @@ -18,125 +18,138 @@ namespace Oqtane.Infrastructure { private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly IWebHostEnvironment _environment; - private readonly IMemoryCache _cache; - public InstallationManager(IHostApplicationLifetime hostApplicationLifetime, IWebHostEnvironment environment, IMemoryCache cache) + public InstallationManager(IHostApplicationLifetime hostApplicationLifetime, IWebHostEnvironment environment) { _hostApplicationLifetime = hostApplicationLifetime; _environment = environment; - _cache = cache; } - public void InstallPackages(string folders) + public void InstallPackages() { - if (!InstallPackages(folders, _environment.WebRootPath, _environment.ContentRootPath)) + if (!InstallPackages(_environment.WebRootPath, _environment.ContentRootPath)) { // error installing packages } } - public static bool InstallPackages(string folders, string webRootPath, string contentRootPath) + public static bool InstallPackages(string webRootPath, string contentRootPath) { bool install = false; string binPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - foreach (string folder in folders.Split(',')) + string sourceFolder = Path.Combine(webRootPath, "Packages"); + if (!Directory.Exists(sourceFolder)) { - string sourceFolder = Path.Combine(webRootPath, folder); - if (!Directory.Exists(sourceFolder)) - { - Directory.CreateDirectory(sourceFolder); - } + Directory.CreateDirectory(sourceFolder); + } - // iterate through Nuget packages in source folder - foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg")) + // support for legacy folder locations + foreach (var folder in "Modules,Themes".Split(",")) + { + foreach(var file in Directory.GetFiles(Path.Combine(webRootPath, folder), "*.nupkg")) { - // iterate through files - using (ZipArchive archive = ZipFile.OpenRead(packagename)) + var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file)); + if (File.Exists(destinationFile)) { - string frameworkversion = ""; - // locate nuspec - foreach (ZipArchiveEntry entry in archive.Entries) + File.Delete(destinationFile); + } + File.Move(file, destinationFile); + } + } + + // iterate through Nuget packages in source folder + foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg")) + { + // iterate through files + using (ZipArchive archive = ZipFile.OpenRead(packagename)) + { + string frameworkversion = ""; + // locate nuspec + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (entry.FullName.ToLower().EndsWith(".nuspec")) { - if (entry.FullName.ToLower().EndsWith(".nuspec")) + // open nuspec + XmlTextReader reader = new XmlTextReader(entry.Open()); + reader.Namespaces = false; // remove namespace + XmlDocument doc = new XmlDocument(); + doc.Load(reader); + // get framework dependency + XmlNode node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']"); + if (node != null) { - // open nuspec - XmlTextReader reader = new XmlTextReader(entry.Open()); - reader.Namespaces = false; // remove namespace - XmlDocument doc = new XmlDocument(); - doc.Load(reader); - // get framework dependency - XmlNode node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']"); - if (node != null) - { - frameworkversion = node.Attributes["version"].Value; - } - - reader.Close(); - } - } - - // if compatible with framework version - if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0) - { - List assets = new List(); - bool manifest = false; - - // packages are in form of name.1.0.0.nupkg or name.culture.1.0.0.nupkg - string name = Path.GetFileNameWithoutExtension(packagename); - string[] segments = name?.Split('.'); - if (segments != null) name = string.Join('.', segments, 0, segments.Length - 3); // remove version information - - // deploy to appropriate locations - foreach (ZipArchiveEntry entry in archive.Entries) - { - string filename = ""; - - // evaluate entry root folder - switch (entry.FullName.Split('/')[0]) - { - case "lib": // lib/net5.0/... - filename = ExtractFile(entry, binPath, 2); - break; - case "wwwroot": // wwwroot/... - filename = ExtractFile(entry, webRootPath, 1); - break; - case "runtimes": // runtimes/name/... - filename = ExtractFile(entry, binPath, 0); - break; - } - - if (filename != "") - { - assets.Add(filename.Replace(contentRootPath, "")); - if (!manifest && Path.GetFileName(filename) == "assets.json") - { - manifest = true; - } - } - } - - // save dynamic list of assets - if (!manifest && assets.Count != 0) - { - string manifestpath = Path.Combine(webRootPath, folder, name, "assets.json"); - if (File.Exists(manifestpath)) - { - File.Delete(manifestpath); - } - if (!Directory.Exists(Path.GetDirectoryName(manifestpath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(manifestpath)); - } - File.WriteAllText(manifestpath, JsonSerializer.Serialize(assets)); + frameworkversion = node.Attributes["version"].Value; } + reader.Close(); + break; } } - // remove package - File.Delete(packagename); - install = true; + // if compatible with framework version + if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0) + { + List assets = new List(); + bool manifest = false; + + // packages are in form of name.1.0.0.nupkg or name.culture.1.0.0.nupkg + string name = Path.GetFileNameWithoutExtension(packagename); + string[] segments = name?.Split('.'); + // remove version information from name + if (segments != null) name = string.Join('.', segments, 0, segments.Length - 3); + + // deploy to appropriate locations + foreach (ZipArchiveEntry entry in archive.Entries) + { + string filename = ""; + + // evaluate entry root folder + switch (entry.FullName.Split('/')[0]) + { + case "lib": // lib/net5.0/... + filename = ExtractFile(entry, binPath, 2); + break; + case "wwwroot": // wwwroot/... + filename = ExtractFile(entry, webRootPath, 1); + break; + case "runtimes": // runtimes/name/... + filename = ExtractFile(entry, binPath, 0); + break; + case "ref": // ref/net5.0/... + filename = ExtractFile(entry, Path.Combine(binPath, "ref"), 2); + break; + } + + if (filename != "") + { + assets.Add(filename.Replace(contentRootPath, "")); + if (!manifest && Path.GetFileName(filename) == name + ".log") + { + manifest = true; + } + } + } + + // save dynamic list of assets + if (!manifest && assets.Count != 0) + { + string manifestpath = Path.Combine(sourceFolder, name + ".log"); + if (File.Exists(manifestpath)) + { + File.Delete(manifestpath); + } + if (!Directory.Exists(Path.GetDirectoryName(manifestpath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(manifestpath)); + } + File.WriteAllText(manifestpath, JsonSerializer.Serialize(assets)); + } + } } + + // remove package + File.Delete(packagename); + install = true; } return install; @@ -163,6 +176,31 @@ namespace Oqtane.Infrastructure return filename; } + public bool UninstallPackage(string PackageName) + { + if (File.Exists(Path.Combine(_environment.WebRootPath, "Packages", PackageName + ".log"))) + { + // use manifest to clean up file resources + List assets = JsonSerializer.Deserialize>(File.ReadAllText(Path.Combine(_environment.WebRootPath, "Packages", PackageName + ".log"))); + assets.Reverse(); + foreach (string asset in assets) + { + // legacy support for assets that were stored as absolute paths + string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; + if (File.Exists(filepath)) + { + File.Delete(filepath); + if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any()) + { + Directory.Delete(Path.GetDirectoryName(filepath)); + } + } + } + return true; + } + return false; + } + public void UpgradeFramework() { string folder = Path.Combine(_environment.WebRootPath, "Framework"); diff --git a/Oqtane.Server/Infrastructure/Interfaces/IInstallationManager.cs b/Oqtane.Server/Infrastructure/Interfaces/IInstallationManager.cs index b491987c..5c2f2853 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/IInstallationManager.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/IInstallationManager.cs @@ -2,7 +2,8 @@ namespace Oqtane.Infrastructure { public interface IInstallationManager { - void InstallPackages(string folders); + void InstallPackages(); + bool UninstallPackage(string PackageName); void UpgradeFramework(); void RestartApplication(); } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 9574ac58..1b72d3f8 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -96,7 +96,14 @@ namespace Oqtane.Repository var ids = new HashSet(moduleDefinitions.Select(item => item.ModuleDefinitionId)); foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId))) { - _permissions.DeletePermission(permission.PermissionId); + try + { + _permissions.DeletePermission(permission.PermissionId); + } + catch + { + // multi-threading can cause a race condition to occur + } } } else diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 8ddb3c10..1188e136 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -174,7 +174,7 @@ namespace Oqtane services.AddSingleton(); // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) - InstallationManager.InstallPackages("Modules,Themes,Packages", _env.WebRootPath, _env.ContentRootPath); + InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath); // register transient scoped core services services.AddTransient(); diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Blogs.log b/Oqtane.Server/wwwroot/Packages/Oqtane.Blogs.log new file mode 100644 index 00000000..cbd320d1 --- /dev/null +++ b/Oqtane.Server/wwwroot/Packages/Oqtane.Blogs.log @@ -0,0 +1 @@ +["C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.dll","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.pdb","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.dll","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.pdb","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.dll","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.pdb","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.css","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.js"] \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.log b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.log new file mode 100644 index 00000000..9be42acf --- /dev/null +++ b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.log @@ -0,0 +1 @@ +["C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Database.SqlServer.dll","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Database.SqlServer.pdb","C:\\Users\\Shaun.Walker\\source\\repos\\sbwalker\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Microsoft.EntityFrameworkCore.SqlServer.dll"] \ No newline at end of file