Merge pull request #502 from sbwalker/master

Optimized downloading of assemblies when using WebAssembly
This commit is contained in:
Shaun Walker 2020-05-18 18:01:23 -04:00 committed by GitHub
commit 9b65cd0e07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 45 deletions

View File

@ -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<List<string>>($"/~/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<string, byte[]> dlls = new Dictionary<string, byte[]>();
Dictionary<string, byte[]> pdbs = new Dictionary<string, byte[]>();
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);
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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/<controller>/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;
}
}
}
}

View File

@ -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/<controller>/load
[HttpGet("load")]
public List<string> Load()
{
List<string> list = new List<string>();
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/<controller>/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/<controller>?moduleid=x
[HttpPost]
[Authorize(Roles = Constants.HostRole)]

View File

@ -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;

View File

@ -76,7 +76,8 @@ namespace System.Reflection
public static IEnumerable<Assembly> GetOqtaneClientAssemblies(this AppDomain appDomain)
{
return appDomain.GetOqtaneAssemblies()
.Where(a => a.GetTypes<IModuleControl>().Any() || a.GetTypes<IThemeControl>().Any() || a.GetTypes<IClientStartup>().Any());
.Where(a => a.GetTypes<IModuleControl>().Any() || a.GetTypes<IThemeControl>().Any() || a.GetTypes<IClientStartup>().Any())
.Where(a => Utilities.GetFullTypeName(a.GetName().Name) != "Oqtane.Client");
}
}
}