From 1532eb7586d7b7a1c179b72bf9fad38346823533 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 18 May 2020 18:02:23 -0400 Subject: [PATCH] Optimized downloading of assemblies when using WebAssembly --- Oqtane.Client/Program.cs | 53 +++++++++++++++--- .../Services/ModuleDefinitionService.cs | 2 +- .../Controllers/InstallationController.cs | 56 +++++++++++++++++++ .../Controllers/ModuleDefinitionController.cs | 35 ------------ .../Infrastructure/InstallationManager.cs | 7 +++ .../Extensions/AssemblyExtensions.cs | 3 +- 6 files changed, 111 insertions(+), 45 deletions(-) diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 26bac719..8a1fd2b8 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -12,6 +12,8 @@ using Oqtane.Modules; using Oqtane.Shared; using Oqtane.Providers; using Microsoft.AspNetCore.Components.Authorization; +using System.IO.Compression; +using System.IO; namespace Oqtane.Client { @@ -90,15 +92,50 @@ namespace Oqtane.Client private static async Task LoadClientAssemblies(HttpClient http) { - var list = await http.GetFromJsonAsync>($"/~/api/ModuleDefinition/load"); - // get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain ) - var assemblyList = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList(); - foreach (var name in list) + // 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 + var zip = await http.GetByteArrayAsync($"/~/api/Installation/load"); + + // asemblies and debug symbols are packaged in a zip file + using (ZipArchive archive = new ZipArchive(new MemoryStream(zip))) { - if (assemblyList.Contains(name)) continue; - // download assembly from server and load - var bytes = await http.GetByteArrayAsync($"/~/api/ModuleDefinition/load/{name}.dll"); - Assembly.Load(bytes); + Dictionary dlls = new Dictionary(); + Dictionary pdbs = new Dictionary(); + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.Name))) + { + using (var memoryStream = new MemoryStream()) + { + entry.Open().CopyTo(memoryStream); + byte[] file = memoryStream.ToArray(); + switch (Path.GetExtension(entry.Name)) + { + case ".dll": + dlls.Add(entry.Name, file); + break; + case ".pdb": + pdbs.Add(entry.Name, file); + break; + } + } + } + } + + foreach (var item in dlls) + { + if (pdbs.ContainsKey(item.Key)) + { + Assembly.Load(item.Value, pdbs[item.Key]); + } + else + { + Assembly.Load(item.Value); + } + } } } } diff --git a/Oqtane.Client/Services/ModuleDefinitionService.cs b/Oqtane.Client/Services/ModuleDefinitionService.cs index 2159a25d..8486e417 100644 --- a/Oqtane.Client/Services/ModuleDefinitionService.cs +++ b/Oqtane.Client/Services/ModuleDefinitionService.cs @@ -51,7 +51,7 @@ namespace Oqtane.Services public async Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId) { - await PostJsonAsync($"{Apiurl}?moduleid={moduleId.ToString()}", moduleDefinition); + await PostJsonAsync($"{Apiurl}?moduleid={moduleId}", moduleDefinition); } } } diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index b16f54f6..a66d6b21 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -4,6 +4,11 @@ using Microsoft.Extensions.Configuration; using Oqtane.Models; using Oqtane.Shared; using Oqtane.Infrastructure; +using System; +using System.IO; +using System.Reflection; +using System.Linq; +using System.IO.Compression; namespace Oqtane.Controllers { @@ -55,5 +60,56 @@ namespace Oqtane.Controllers _installationManager.UpgradeFramework(); return installation; } + + // GET api//load + [HttpGet("load")] + public IActionResult Load() + { + if (_config.GetSection("Runtime").Value == "WebAssembly") + { + // get list of assemblies which should be downloaded to browser + var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); + var list = assemblies.Select(a => a.GetName().Name).ToList(); + var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct(); + list.AddRange(deps.Where(a => a.Name.EndsWith(".oqtane", StringComparison.OrdinalIgnoreCase)).Select(a => a.Name)); + + // create zip file containing assemblies and debug symbols + string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + byte[] zipfile; + using (var memoryStream = new MemoryStream()) + { + using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) + { + ZipArchiveEntry entry; + foreach (string file in list) + { + entry = archive.CreateEntry(file + ".dll"); + using (var filestream = new FileStream(Path.Combine(binfolder, file + ".dll"), FileMode.Open, FileAccess.Read)) + using (var entrystream = entry.Open()) + { + filestream.CopyTo(entrystream); + } + + if (System.IO.File.Exists(Path.Combine(binfolder, file + ".pdb"))) + { + entry = archive.CreateEntry(file + ".pdb"); + using (var filestream = new FileStream(Path.Combine(binfolder, file + ".pdb"), FileMode.Open, FileAccess.Read)) + using (var entrystream = entry.Open()) + { + filestream.CopyTo(entrystream); + } + } + } + } + zipfile = memoryStream.ToArray(); + } + return File(zipfile, "application/octet-stream", "oqtane.zip"); + } + else + { + HttpContext.Response.StatusCode = 401; + return null; + } + } } } diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 29dd91d2..75b2f844 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -15,8 +15,6 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using System.Xml.Linq; -using Microsoft.AspNetCore.Mvc.Formatters; -// ReSharper disable StringIndexOfIsCultureSpecific.1 namespace Oqtane.Controllers { @@ -163,39 +161,6 @@ namespace Oqtane.Controllers } } - // GET api//load - [HttpGet("load")] - public List Load() - { - List list = new List(); - if (_config.GetSection("Runtime").Value == "WebAssembly") - { - var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); - list = AppDomain.CurrentDomain.GetOqtaneClientAssemblies().Select(a => a.GetName().Name).ToList(); - var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct(); - list.AddRange(deps.Where(a => a.Name.EndsWith(".oqtane", StringComparison.OrdinalIgnoreCase)).Select(a => a.Name)); - } - return list; - } - - // GET api//load/assembyname - [HttpGet("load/{assemblyname}")] - public IActionResult Load(string assemblyname) - { - if (_config.GetSection("Runtime").Value == "WebAssembly" && Path.GetExtension(assemblyname).ToLower() == ".dll") - { - string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - byte[] file = System.IO.File.ReadAllBytes(Path.Combine(binfolder, assemblyname)); - return File(file, "application/octet-stream", assemblyname); - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Download Assembly {Assembly}", assemblyname); - HttpContext.Response.StatusCode = 401; - return null; - } - } - // POST api/?moduleid=x [HttpPost] [Authorize(Roles = Constants.HostRole)] diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index adce62e9..a2c0a330 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -92,6 +92,13 @@ namespace Oqtane.Infrastructure switch (foldername) { + case "": + if (filename.EndsWith(".nuspec")) + { + filename = Path.Combine(sourceFolder, name); + entry.ExtractToFile(filename, true); + } + break; case "lib": if (binFolder != null) entry.ExtractToFile(Path.Combine(binFolder, filename), true); break; diff --git a/Oqtane.Shared/Extensions/AssemblyExtensions.cs b/Oqtane.Shared/Extensions/AssemblyExtensions.cs index 4742d368..fc873fe2 100644 --- a/Oqtane.Shared/Extensions/AssemblyExtensions.cs +++ b/Oqtane.Shared/Extensions/AssemblyExtensions.cs @@ -76,7 +76,8 @@ namespace System.Reflection public static IEnumerable GetOqtaneClientAssemblies(this AppDomain appDomain) { return appDomain.GetOqtaneAssemblies() - .Where(a => a.GetTypes().Any() || a.GetTypes().Any() || a.GetTypes().Any()); + .Where(a => a.GetTypes().Any() || a.GetTypes().Any() || a.GetTypes().Any()) + .Where(a => Utilities.GetFullTypeName(a.GetName().Name) != "Oqtane.Client"); } } }