Skip to content

Commit

Permalink
Merge pull request #1828 from PrismLibrary/core3-directorymodulecatalog
Browse files Browse the repository at this point in the history
Updated .NET Core 3 DirectoryModuleCatalog
  • Loading branch information
brianlagunas committed Jun 7, 2019
2 parents b4fb6c1 + 192735c commit f76990b
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 20 deletions.
212 changes: 212 additions & 0 deletions Source/Wpf/Prism.Wpf/Modularity/DirectoryModuleCatalog.Core.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#if NETCOREAPP3_0
using Prism.Properties;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Prism.Modularity
{
/// <summary>
/// Represets a catalog created from a directory on disk.
/// </summary>
/// <remarks>
/// The directory catalog will scan the contents of a directory, locating classes that implement
/// <see cref="IModule"/> and add them to the catalog based on contents in their associated <see cref="ModuleAttribute"/>.
/// Assemblies are loaded into a new application domain with ReflectionOnlyLoad. The application domain is destroyed
/// once the assemblies have been discovered.
///
/// The diretory catalog does not continue to monitor the directory after it has created the initialze catalog.
/// </remarks>
public class DirectoryModuleCatalog : ModuleCatalog
{
/// <summary>
/// Directory containing modules to search for.
/// </summary>
public string ModulePath { get; set; }

/// <summary>
/// Drives the main logic of building the child domain and searching for the assemblies.
/// </summary>
protected override void InnerLoad()
{
if (string.IsNullOrEmpty(this.ModulePath))
throw new InvalidOperationException(Resources.ModulePathCannotBeNullOrEmpty);

if (!Directory.Exists(this.ModulePath))
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture, Resources.DirectoryNotFound, this.ModulePath));

AppDomain childDomain = AppDomain.CurrentDomain;

try
{
List<string> loadedAssemblies = new List<string>();

var assemblies = (
from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
where !(assembly is System.Reflection.Emit.AssemblyBuilder)
&& assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
&& !String.IsNullOrEmpty(assembly.Location)
select assembly.Location
);

loadedAssemblies.AddRange(assemblies);

Type loaderType = typeof(InnerModuleInfoLoader);

if (loaderType.Assembly != null)
{
var loader =
(InnerModuleInfoLoader)
childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();

this.Items.AddRange(loader.GetModuleInfos(this.ModulePath));
}
}
catch (Exception ex)
{
throw new Exception("There was an error loading assemblies.", ex);
}
}

private class InnerModuleInfoLoader : MarshalByRefObject
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal ModuleInfo[] GetModuleInfos(string path)
{
DirectoryInfo directory = new DirectoryInfo(path);

ResolveEventHandler resolveEventHandler =
delegate (object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };

AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;

Assembly moduleReflectionOnlyAssembly = AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.FullName == typeof(IModule).Assembly.FullName);
Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);

IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(directory, IModuleType);

var array = modules.ToArray();
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
return array;
}

private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
{
List<Assembly> validAssemblies = new List<Assembly>();
Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic).ToArray();

var fileInfos = directory.GetFiles("*.dll")
.Where(file => alreadyLoadedAssemblies.FirstOrDefault(
assembly => String.Compare(Path.GetFileName(assembly.Location),
file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null).ToList();

foreach (FileInfo fileInfo in fileInfos)
{
try
{
validAssemblies.Add(Assembly.LoadFrom(fileInfo.FullName));
}
catch (BadImageFormatException)
{
// skip non-.NET Dlls
}
}

return validAssemblies.SelectMany(assembly => assembly
.GetExportedTypes()
.Where(IModuleType.IsAssignableFrom)
.Where(t => t != IModuleType)
.Where(t => !t.IsAbstract)
.Select(type => CreateModuleInfo(type)));
}

private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
{
Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
AssemblyName assemblyName = new AssemblyName(args.Name);
string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal void LoadAssemblies(IEnumerable<string> assemblies)
{
foreach (string assemblyPath in assemblies)
{
try
{
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
// Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
}
}
}

private static ModuleInfo CreateModuleInfo(Type type)
{
string moduleName = type.Name;
List<string> dependsOn = new List<string>();
bool onDemand = false;
var moduleAttribute =
CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);

if (moduleAttribute != null)
{
foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
{
string argumentName = argument.MemberInfo.Name;
switch (argumentName)
{
case "ModuleName":
moduleName = (string)argument.TypedValue.Value;
break;

case "OnDemand":
onDemand = (bool)argument.TypedValue.Value;
break;

case "StartupLoaded":
onDemand = !((bool)argument.TypedValue.Value);
break;
}
}
}

var moduleDependencyAttributes =
CustomAttributeData.GetCustomAttributes(type).Where(
cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);

foreach (CustomAttributeData cad in moduleDependencyAttributes)
{
dependsOn.Add((string)cad.ConstructorArguments[0].Value);
}

ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
{
InitializationMode = onDemand ? InitializationMode.OnDemand : InitializationMode.WhenAvailable,
Ref = type.Assembly.EscapedCodeBase,
};
moduleInfo.DependsOn.AddRange(dependsOn);
return moduleInfo;
}
}
}
}
#endif
35 changes: 15 additions & 20 deletions Source/Wpf/Prism.Wpf/Modularity/DirectoryModuleCatalog.Desktop.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

#if NET45

using Prism.Properties;
using System;
Expand Down Expand Up @@ -41,11 +41,8 @@ protected override void InnerLoad()
if (!Directory.Exists(this.ModulePath))
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture, Resources.DirectoryNotFound, this.ModulePath));
#if NETCOREAPP3_0
AppDomain childDomain = AppDomain.CurrentDomain;
#else

AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);
#endif

try
{
Expand All @@ -54,7 +51,7 @@ protected override void InnerLoad()
var assemblies = (
from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
where !(assembly is System.Reflection.Emit.AssemblyBuilder)
&& assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
&& assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
&& !String.IsNullOrEmpty(assembly.Location)
select assembly.Location
);
Expand All @@ -74,13 +71,11 @@ select assembly.Location
}
finally
{
#if NET45
AppDomain.Unload(childDomain);
#endif
}
}

#if NET45

/// <summary>
/// Creates a new child domain and copies the evidence from a parent domain.
/// </summary>
Expand All @@ -105,7 +100,6 @@ protected virtual AppDomain BuildChildDomain(AppDomain parentDomain)
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);
}
#endif

private class InnerModuleInfoLoader : MarshalByRefObject
{
Expand All @@ -115,7 +109,7 @@ internal ModuleInfo[] GetModuleInfos(string path)
DirectoryInfo directory = new DirectoryInfo(path);

ResolveEventHandler resolveEventHandler =
delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };
delegate (object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };

AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;

Expand Down Expand Up @@ -214,15 +208,15 @@ private static ModuleInfo CreateModuleInfo(Type type)
switch (argumentName)
{
case "ModuleName":
moduleName = (string) argument.TypedValue.Value;
moduleName = (string)argument.TypedValue.Value;
break;

case "OnDemand":
onDemand = (bool) argument.TypedValue.Value;
onDemand = (bool)argument.TypedValue.Value;
break;

case "StartupLoaded":
onDemand = !((bool) argument.TypedValue.Value);
onDemand = !((bool)argument.TypedValue.Value);
break;
}
}
Expand All @@ -234,20 +228,21 @@ private static ModuleInfo CreateModuleInfo(Type type)

foreach (CustomAttributeData cad in moduleDependencyAttributes)
{
dependsOn.Add((string) cad.ConstructorArguments[0].Value);
dependsOn.Add((string)cad.ConstructorArguments[0].Value);
}

ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
{
InitializationMode =
{
InitializationMode =
onDemand
? InitializationMode.OnDemand
: InitializationMode.WhenAvailable,
Ref = type.Assembly.EscapedCodeBase,
};
Ref = type.Assembly.EscapedCodeBase,
};
moduleInfo.DependsOn.AddRange(dependsOn);
return moduleInfo;
}
}
}
}
}
#endif

0 comments on commit f76990b

Please sign in to comment.