Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed compatibility issue in .NET5/WebAssembly where assemblies were not being loaded into the default AppDomain, optimized service registration on WebAssembly, fixed spelling mistake for satellite assemblies constant and fixed issue in LocalizableComponent #857

Merged
merged 2 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Oqtane.Client/Modules/Controls/LocalizableComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protected string Localize(string name)
{
if (!IsLocalizable)
{
return null;
return name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

@sbwalker sbwalker Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For modules that have been created for Oqtane 1.x they may have used the ActionDialog control. When trying to run these modules on Oqtane 2.0 (.NET5) the modules run fine however when the ActionDialog modal popup is displayed, the buttons do not have any text. I stepped through the code and it is because the Localize() method is returning null. This appears to be a breaking change as it affects backward compatibility. I am not sure if the solution I checked in is valid - if not, then we will need some other type of solution to deal with this issue. It does seem to make sense to me that if a component is not localizable then the method should return the text which was passed in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, I did that because you mentioned this in the PR about hating default values in localization ;)

}

var key = $"{ResourceKey}.{name}";
Expand Down
49 changes: 20 additions & 29 deletions Oqtane.Client/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using System.Runtime.Loader;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -64,30 +65,28 @@ public static async Task Main(string[] args)

await LoadClientAssemblies(httpClient);

// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
var implementationTypes = assembly.GetTypes()
.Where(item => item.GetInterfaces().Contains(typeof(IService)));

foreach (Type implementationtype in implementationTypes)
// dynamically register module services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
if (servicetype != null)
{
builder.Services.AddScoped(servicetype, implementationtype); // traditional service interface
}
else
if (implementationType.AssemblyQualifiedName != null)
{
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
builder.Services.AddScoped(serviceType ?? implementationType, implementationType);
}
}

assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(builder.Services));
// register client startup services
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
}

var host = builder.Build();

ServiceActivator.Configure(host.Services);
Expand Down Expand Up @@ -120,15 +119,7 @@ private static async Task LoadClientAssemblies(HttpClient http)
switch (Path.GetExtension(entry.Name))
{
case ".dll":
// Loads the stallite assemblies early
if (entry.Name.EndsWith(Constants.StalliteAssemblyExtension))
{
Assembly.Load(file);
}
else
{
dlls.Add(entry.Name, file);
}
dlls.Add(entry.Name, file);
break;
case ".pdb":
pdbs.Add(entry.Name, file);
Expand All @@ -142,11 +133,11 @@ private static async Task LoadClientAssemblies(HttpClient http)
{
if (pdbs.ContainsKey(item.Key))
{
Assembly.Load(item.Value, pdbs[item.Key]);
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key]));
}
else
{
Assembly.Load(item.Value);
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Oqtane.Client/UI/SiteRouter.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@using System.Diagnostics.CodeAnalysis
@using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
Expand Down Expand Up @@ -442,7 +442,7 @@
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
if (module.PageId == page.PageId || module.ModuleId == moduleid)
if ((module.PageId == page.PageId || module.ModuleId == moduleid) && module.ModuleDefinition != null)
{
var typename = string.Empty;
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(GetRuntime().ToString())))
Expand Down
14 changes: 7 additions & 7 deletions Oqtane.Server/Controllers/InstallationController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Reflection;
using System.Linq;
Expand Down Expand Up @@ -71,12 +71,12 @@ public IActionResult Load()
{
if (_config.GetSection("Runtime").Value == "WebAssembly")
{
// get list of assemblies which should be downloaded to browser
// get list of assemblies which should be downloaded to client
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
var list = assemblies.Select(a => a.GetName().Name).ToList();
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

// Get the satellite assemblies
// insert satellite assemblies at beginning of list
foreach (var culture in _localizationManager.GetSupportedCultures())
{
var assembliesFolderPath = Path.Combine(binFolder, culture);
Expand All @@ -89,7 +89,7 @@ public IActionResult Load()
{
foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath))
{
list.Add(Path.Combine(culture, Path.GetFileNameWithoutExtension(resourceFile)));
list.Insert(0, Path.Combine(culture, Path.GetFileNameWithoutExtension(resourceFile)));
}
}
else
Expand All @@ -98,23 +98,23 @@ public IActionResult Load()
}
}

// get module and theme dependencies
// insert module and theme dependencies at beginning of list
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
{
var instance = Activator.CreateInstance(type) as IModule;
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (!list.Contains(name)) list.Add(name);
if (!list.Contains(name)) list.Insert(0, name);
}
}
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
{
var instance = Activator.CreateInstance(type) as ITheme;
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (!list.Contains(name)) list.Add(name);
if (!list.Contains(name)) list.Insert(0, name);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -32,6 +32,7 @@ private static IServiceCollection AddOqtaneServices(this IServiceCollection serv
}

var hostedServiceType = typeof(IHostedService);

var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
Expand All @@ -56,6 +57,7 @@ private static IServiceCollection AddOqtaneServices(this IServiceCollection serv
}
}

// register server startup services
var startUps = assembly.GetInstances<IServerStartup>();
foreach (var startup in startUps)
{
Expand All @@ -64,6 +66,7 @@ private static IServiceCollection AddOqtaneServices(this IServiceCollection serv

if (runtime == Runtime.Server)
{
// register client startup services if running on server
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(services));
Expand Down Expand Up @@ -143,7 +146,7 @@ private static void LoadSatelliteAssemblies(string[] supportedCultures)
var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture));
if (assembliesFolder.Exists)
{
foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.StalliteAssemblyExtension))
foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.SatelliteAssemblyExtension))
{
AssemblyName assemblyName;
try
Expand Down
3 changes: 1 addition & 2 deletions Oqtane.Shared/Extensions/AssemblyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Oqtane.Modules;
Expand Down Expand Up @@ -34,7 +34,6 @@ public static IEnumerable<Type> GetTypes(this Assembly assembly, Type interfaceT
}

return assembly.GetTypes()
//.Where(t => t.GetInterfaces().Contains(interfaceType));
.Where(x => !x.IsInterface && !x.IsAbstract && interfaceType.IsAssignableFrom(x));
}

Expand Down
4 changes: 2 additions & 2 deletions Oqtane.Shared/Shared/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;

namespace Oqtane.Shared {
Expand Down Expand Up @@ -68,7 +68,7 @@ public class Constants {
};
public static readonly string[] InvalidFileNameEndingChars = { ".", " " };

public static readonly string StalliteAssemblyExtension = ".resources.dll";
public static readonly string SatelliteAssemblyExtension = ".resources.dll";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch


public static readonly string DefaultCulture = CultureInfo.InstalledUICulture.Name;
}
Expand Down