質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
.NET

.NETとは、主に.NET Frameworkと呼ばれるアプリケーションまたは開発環境を指します。CLR(共通言語ランタイム)を搭載し、入力された言語をCIL(共通中間言語)に変換・実行することが可能です。そのため、C#やPythonなど複数の言語を用いることができます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

2回答

211閲覧

WPF Prism 複数フォルダから動的にモジュールを読み込む方法(単一ファイル)

sakizakino

総合スコア13

.NET

.NETとは、主に.NET Frameworkと呼ばれるアプリケーションまたは開発環境を指します。CLR(共通言語ランタイム)を搭載し、入力された言語をCIL(共通中間言語)に変換・実行することが可能です。そのため、C#やPythonなど複数の言語を用いることができます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

1グッド

0クリップ

投稿2024/03/15 03:03

編集2024/03/15 04:11

実現したいこと

「Modules」と「Plugins」というフォルダがあり、それぞれのフォルダに配置されているモジュールを動的に読み込みし、単一ファイルでアプリケーションを発行したいです。(「Modules」は遅延読み込み、「Plugins」は起動時に読み込み)

前提

アプリケーション本体と、複数のモジュールで構成されているアプリケーションをPrismで作成しております。

「Modules」には50個ほどのプロジェクトがあり、こちらは呼び出し時にロードされる遅延読み込みをしております。(Prism.Modularity.InitializationMode.OnDemand)

「Plugins」には機能拡張用の便利ツール等の独立したプロジェクトを配置し、アプリケーション起動時に読み込みされ、Menu要素に表示しております。(Prism.Modularity.InitializationMode.WhenAvailable)

どちらのモジュールも要件によって動的に配置され、アプリケーション本体は何のモジュールがあるかを知りません。

発生している問題・エラーメッセージ

上記の動作は、以下のサイトを参考にして、通常のアプリケーションの発行方法では実現できております。
Multiple DirectoryModuleCatalog in a Prism application(stack overflow)

App.xaml.cs

1protected override IModuleCatalog CreateModuleCatalog() 2{ 3 var path = Path.GetDirectoryName(AppContext.BaseDirectory); 4 var directories = new string[] { "Modules", "Plugins" }; 5 List<IModuleCatalogItem> components = []; 6 var catalog = new ModuleCatalog(); 7 8 foreach (var dir in directories) 9 { 10 var dirCatalog = new DirectoryModuleCatalog() { ModulePath = $"{path}\\{dir}" }; 11 dirCatalog.Initialize(); // ★単一ファイルで発行した場合、ここでエラーが発生 12 components.AddRange(dirCatalog.Items); 13 } 14 foreach (var component in components) 15 { 16 catalog.Items.Add(component); 17 } 18 // 呼び出し時にモジュールを読み込む(遅延読み込み) 19 foreach (var moduleInfo in catalog.Modules) 20 { 21 if (Path.GetFileName(Path.GetDirectoryName(moduleInfo.Ref)) == "Modules") 22 moduleInfo.InitializationMode = InitializationMode.OnDemand; 23 } 24 return catalog; 25}

しかし、単一ファイルで発行した場合には、上記エラー発生個所のPrism内部で、.NET非互換性APIのAssembly.Location等が使用されており、Pathが空文字になるためエラーが発生しているように見受けられます。

DirectoryModuleCatalog.netcore.cs
[Prism.Modularity]

DirectoryModuleCatalog.netcore.cs

1/// <summary> 2/// Drives the main logic of building the child domain and searching for the assemblies. 3/// </summary> 4protected override void InnerLoad() 5{ 6 if (string.IsNullOrEmpty(this.ModulePath)) 7 throw new InvalidOperationException(Resources.ModulePathCannotBeNullOrEmpty); 8 9 if (!Directory.Exists(this.ModulePath)) 10 throw new InvalidOperationException( 11 string.Format(CultureInfo.CurrentCulture, Resources.DirectoryNotFound, this.ModulePath)); 12 13 AppDomain childDomain = AppDomain.CurrentDomain; 14 15 try 16 { 17 List<string> loadedAssemblies = new List<string>(); 18 19 var assemblies = ( 20 from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies() 21 where !(assembly is System.Reflection.Emit.AssemblyBuilder) 22 && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder" 23 && !String.IsNullOrEmpty(assembly.Location) 24 select assembly.Location 25 ); 26 27 loadedAssemblies.AddRange(assemblies); 28 29 Type loaderType = typeof(InnerModuleInfoLoader); 30 31 if (loaderType.Assembly != null) 32 { 33 var loader = 34 (InnerModuleInfoLoader) 35 childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap(); 36 37 this.Items.AddRange(loader.GetModuleInfos(this.ModulePath)); 38 } 39 } 40 catch (Exception ex) 41 { 42 throw new Exception("There was an error loading assemblies.", ex); 43 } 44}

エラーメッセージ

There was an error loading assemblies.

同じような処理をされている方もいらっしゃるかと思いますので、どのような方法を取っていらっしゃるのか、お力添えいただけると幸いです。
よろしくお願いいたします。

補足情報(FW/ツールのバージョンなど)

Visual Studio 2022 Version 17.8.6
.NET 8
Prism.DryIoc (8.1.97)

なお、平日日中のお返事になりますことをご了承ください。

TN8001😄を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TN8001

2024/03/15 10:00

「自分で読み込んでね」って言ってるような?(自動翻訳なので自信なし) [[Bug] Exception on startup when using DirectoryModuleCatalog in .NET 5.0 single-file WPF application · Issue #2254 · PrismLibrary/Prism](https://github.com/PrismLibrary/Prism/issues/2254) 課金すれば相談に乗ってくれるかもしれませんね😉 わたしは「同じような処理をされている方」ではないです。
sakizakino

2024/03/18 00:13

TN8001様 いつもWPF関連の質問で大変参考にさせていただいております。 該当のIssueの提示、ありがとうございます。 自分は「DirectoryModule」までで検索していたため、見つけられませんでした・・・。 公式がコードで実装しろとの回答でしたので、そちらの方向で検討します。 貴重なお時間を割いていただき、ありがとうございました。
TN8001

2024/03/18 00:30

> 公式がコードで実装しろとの回答でしたので、そちらの方向で検討します。 何かしら成果がございましたら、知見を共有して頂けたらありがたいです。 [ヘルプ|質問をした後に自己解決してしまった](https://teratail.com/help#resolve-myself)
sakizakino

2024/03/18 00:38

解決方法につきましては、しばしお時間をいただき、追記いたします。
guest

回答2

0

自己解決

当初はPrismに用意されている、DirectoryModuleCatalogをどうにかして実現しようとしておりましたが、TN8001様がご提示してくださったIssueや類似のIssueにて、公式が「単一ファイルには対応していない、あなたがプルリクしてね!」というような回答をしていたので、IModuleManagerのLoadModuleを用いて解決しました。

結論として、該当部分は以下のコードになります。

App.xaml.cs

1protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 2{ 3 string appPath = AppContext.BaseDirectory; 4 string modulesPath = Path.Combine(appPath, "Modules"); 5 string pluginsPath = Path.Combine(appPath, "Plugins"); 6 7 string[] modules = Directory.GetFiles(modulesPath, "*.dll", SearchOption.TopDirectoryOnly); 8 string[] plugins = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.TopDirectoryOnly); 9 10 var pluginMenuService = _container.Resolve<IPluginMenuService>(); 11 12 // Modulesフォルダ以下のモジュールを追加 13 foreach (string modulePath in modules) 14 { 15 var assembly = Assembly.LoadFrom(modulePath); 16 string assemblyName = assembly.GetName().Name; // ModuleA 17 18 // [プロジェクト名]Moduleという命名規約にする 19 var moduleName = assembly.GetTypes().Where(x => x.Name == $"{assemblyName}Module").FirstOrDefault(); 20 21 moduleCatalog.AddModule(new ModuleInfo() 22 { 23 ModuleName = moduleName.Name, 24 ModuleType = moduleName.AssemblyQualifiedName, 25 InitializationMode = InitializationMode.OnDemand 26 }); 27 28 pluginMenuService.ModuleNames.Add($"{assemblyName}Module"); 29 } 30 31 // Pluginsフォルダ以下のモジュールを追加 32 foreach (string modulePath in plugins) 33 { 34 var assembly = Assembly.LoadFrom(modulePath); 35 string assemblyName = assembly.GetName().Name; 36 37 var moduleName = assembly.GetTypes().Where(x => x.Name == $"{assemblyName}Module").FirstOrDefault(); 38 39 moduleCatalog.AddModule(new ModuleInfo() 40 { 41 ModuleName = moduleName.Name, 42 ModuleType = moduleName.AssemblyQualifiedName, 43 InitializationMode = InitializationMode.WhenAvailable 44 }); 45 } 46 47 // 参照モジュールの追加 48 moduleCatalog.AddModule<ModuleBModule>(); 49}

MainWindowViewModel.cs

1public MainWindowViewModel(IRegionManager regionManager, IModuleManager moduleManager, IPluginMenuService pluginMenuService) 2{ 3 ModuleMenus = pluginMenuService.ModuleMenus.ToReadOnlyReactiveCollection(); 4 PluginMenus = pluginMenuService.PluginMenus.ToReadOnlyReactiveCollection(); 5 ModuleNames = pluginMenuService.ModuleNames.ToReadOnlyReactiveCollection(); 6 7 AddModuleCommand.Subscribe(x => 8 { 9 moduleManager.LoadModule(x); 10 }); 11 12 NavigateCommand.Subscribe(x => regionManager.RequestNavigate("ContentRegion", x)); 13}

いろいろと雑なのはご容赦ください。
一応、全体のコードも載せておきますが、以下は読み飛ばしてください。

プログラムの構成

ModuleLoadManualSample - アプリケーション本体 - MainWindow

ModuleA - 遅延読み込みモジュール - ViewA
ModuleB - プロジェクト参照モジュール(起動時読み込み) - ViewB
PluginA - 起動時読み込みプラグイン - ViewC

PluginServices - プラグイン用のサービス
PluginServices.Interfaces - プラグイン用のサービスのインターフェース

投稿2024/03/18 06:43

sakizakino

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

インターフェース

MenuBase.cs

1namespace PluginServices.Interfaces.Models 2{ 3 public class MenuBase 4 { 5 public string Header { get; set; } = string.Empty; 6 public string ViewName { get; set; } = string.Empty; 7 } 8}

IPluginMenuService.cs

1using System.Collections.ObjectModel; 2using PluginServices.Interfaces.Models; 3 4namespace PluginServices.Interfaces 5{ 6 public interface IPluginMenuService 7 { 8 ObservableCollection<MenuBase> ModuleMenus { get; } 9 ObservableCollection<MenuBase> PluginMenus { get; } 10 ObservableCollection<string> ModuleNames { get; } 11 } 12}

サービス

PluginMenuService.cs

1using System.Collections.ObjectModel; 2using PluginServices.Interfaces; 3using PluginServices.Interfaces.Models; 4 5namespace PluginServices 6{ 7 public class PluginMenuService : IPluginMenuService 8 { 9 public ObservableCollection<MenuBase> ModuleMenus { get; } = []; 10 public ObservableCollection<MenuBase> PluginMenus { get; } = []; 11 public ObservableCollection<string> ModuleNames { get; } = []; 12 } 13}

モジュール

ModuleAModule.cs

1using System.Windows; 2using ModuleA.Views; 3using PluginServices.Interfaces; 4using Prism.Ioc; 5using Prism.Modularity; 6 7namespace ModuleA 8{ 9 public class ModuleAModule : IModule 10 { 11 public void OnInitialized(IContainerProvider containerProvider) 12 { 13 var pluginMenu = containerProvider.Resolve<IPluginMenuService>(); 14 pluginMenu.ModuleMenus.Add(new() { Header = "モジュールA", ViewName = nameof(ViewA) }); 15 } 16 17 public void RegisterTypes(IContainerRegistry containerRegistry) 18 { 19 containerRegistry.RegisterForNavigation<ViewA>(); 20 } 21 } 22}

ModuleBModule.cs

1namespace ModuleB 2{ 3 public class ModuleBModule : IModule 4 { 5 public void OnInitialized(IContainerProvider containerProvider) 6 { 7 var pluginMenu = containerProvider.Resolve<IPluginMenuService>(); 8 pluginMenu.ModuleMenus.Add(new() { Header = "モジュールB", ViewName = nameof(ViewB) }); 9 } 10 11 public void RegisterTypes(IContainerRegistry containerRegistry) 12 { 13 containerRegistry.RegisterForNavigation<ViewB>(); 14 } 15 } 16}

PluginAModule.cs

1using System.Windows; 2using PluginA.Views; 3using PluginServices.Interfaces; 4using Prism.Ioc; 5using Prism.Modularity; 6 7namespace PluginA 8{ 9 public class PluginAModule : IModule 10 { 11 public void OnInitialized(IContainerProvider containerProvider) 12 { 13 var pluginMenu = containerProvider.Resolve<IPluginMenuService>(); 14 pluginMenu.PluginMenus.Add(new() { Header = "プラグインA", ViewName = nameof(ViewC) }); 15 } 16 17 public void RegisterTypes(IContainerRegistry containerRegistry) 18 { 19 containerRegistry.RegisterForNavigation<ViewC>(); 20 } 21 } 22}

アプリケーション本体

App.xaml.cs

1using System.IO; 2using System.Reflection; 3using System; 4using System.Windows; 5using ModuleLoadManualSample.Views; 6using PluginServices; 7using PluginServices.Interfaces; 8using Prism.Ioc; 9using Prism.Modularity; 10using System.Linq; 11using ModuleB; 12using DryIoc; 13using Prism.DryIoc; 14 15namespace ModuleLoadManualSample 16{ 17 /// <summary> 18 /// Interaction logic for App.xaml 19 /// </summary> 20 public partial class App 21 { 22 IContainer _container; 23 24 protected override Window CreateShell() 25 { 26 return Container.Resolve<MainWindow>(); 27 } 28 29 protected override void RegisterTypes(IContainerRegistry containerRegistry) 30 { 31 containerRegistry.RegisterSingleton<IPluginMenuService, PluginMenuService>(); 32 33 _container = containerRegistry.GetContainer(); 34 } 35 36 protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 37 { 38 string appPath = AppContext.BaseDirectory; 39 string modulesPath = Path.Combine(appPath, "Modules"); 40 string pluginsPath = Path.Combine(appPath, "Plugins"); 41 42 string[] modules = Directory.GetFiles(modulesPath, "*.dll", SearchOption.TopDirectoryOnly); 43 string[] plugins = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.TopDirectoryOnly); 44 45 var pluginMenuService = _container.Resolve<IPluginMenuService>(); 46 47 // Modulesフォルダ以下のモジュールを追加 48 foreach (string modulePath in modules) 49 { 50 var assembly = Assembly.LoadFrom(modulePath); 51 string assemblyName = assembly.GetName().Name; 52 53 // [プロジェクト名]Moduleという命名規約にする 54 var moduleName = assembly.GetTypes().Where(x => x.Name == $"{assemblyName}Module").FirstOrDefault(); 55 56 moduleCatalog.AddModule(new ModuleInfo() 57 { 58 ModuleName = moduleName.Name, 59 ModuleType = moduleName.AssemblyQualifiedName, 60 InitializationMode = InitializationMode.OnDemand 61 }); 62 63 pluginMenuService.ModuleNames.Add($"{assemblyName}Module"); 64 } 65 66 // Pluginsフォルダ以下のモジュールを追加 67 foreach (string modulePath in plugins) 68 { 69 var assembly = Assembly.LoadFrom(modulePath); 70 string assemblyName = assembly.GetName().Name; 71 72 var moduleName = assembly.GetTypes().Where(x => x.Name == $"{assemblyName}Module").FirstOrDefault(); 73 74 moduleCatalog.AddModule(new ModuleInfo() 75 { 76 ModuleName = moduleName.Name, 77 ModuleType = moduleName.AssemblyQualifiedName, 78 InitializationMode = InitializationMode.WhenAvailable 79 }); 80 } 81 82 // 参照モジュールの追加 83 moduleCatalog.AddModule<ModuleBModule>(); 84 } 85 } 86}

MainWindowViewModel.cs

1using System; 2using PluginServices.Interfaces; 3using PluginServices.Interfaces.Models; 4using Prism.Modularity; 5using Prism.Mvvm; 6using Prism.Regions; 7using Reactive.Bindings; 8 9namespace ModuleLoadManualSample.ViewModels 10{ 11 public class MainWindowViewModel : BindableBase 12 { 13 public ReadOnlyReactiveCollection<MenuBase> ModuleMenus { get; } 14 public ReadOnlyReactiveCollection<MenuBase> PluginMenus { get; } 15 public ReadOnlyReactiveCollection<string> ModuleNames { get; } 16 17 public ReactiveCommand<string> AddModuleCommand { get; } = new(); 18 public ReactiveCommand<string> NavigateCommand { get; } = new(); 19 20 public MainWindowViewModel(IRegionManager regionManager, IModuleManager moduleManager, IPluginMenuService pluginMenuService) 21 { 22 ModuleMenus = pluginMenuService.ModuleMenus.ToReadOnlyReactiveCollection(); 23 PluginMenus = pluginMenuService.PluginMenus.ToReadOnlyReactiveCollection(); 24 ModuleNames = pluginMenuService.ModuleNames.ToReadOnlyReactiveCollection(); 25 26 AddModuleCommand.Subscribe(x => 27 { 28 moduleManager.LoadModule(x); 29 }); 30 31 NavigateCommand.Subscribe(x => regionManager.RequestNavigate("ContentRegion", x)); 32 } 33 } 34}

MainWindow.xaml

1<Window x:Class="ModuleLoadManualSample.Views.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:prism="http://prismlibrary.com/" 5 xmlns:viewmodels="clr-namespace:ModuleLoadManualSample.ViewModels" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 d:DataContext="{d:DesignInstance Type=viewmodels:MainWindowViewModel}" 8 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 9 mc:Ignorable="d" 10 prism:ViewModelLocator.AutoWireViewModel="True" 11 Title="ModuleLoadManual" Height="350" Width="525" > 12 <Grid> 13 <Grid.RowDefinitions> 14 <RowDefinition Height="Auto"/> 15 <RowDefinition/> 16 </Grid.RowDefinitions> 17 <Menu> 18 <MenuItem Header="モジュールの追加" ItemsSource="{Binding ModuleNames}"> 19 <MenuItem.ItemContainerStyle> 20 <Style TargetType="MenuItem"> 21 <Setter Property="Header" Value="{Binding }"/> 22 <Setter Property="Command" Value="{Binding DataContext.AddModuleCommand, RelativeSource={RelativeSource AncestorType=Window}}"/> 23 <Setter Property="CommandParameter" Value="{Binding }"/> 24 </Style> 25 </MenuItem.ItemContainerStyle> 26 </MenuItem> 27 <MenuItem Header="モジュール" ItemsSource="{Binding ModuleMenus}"> 28 <MenuItem.ItemContainerStyle> 29 <Style TargetType="MenuItem"> 30 <Setter Property="Header" Value="{Binding Header}"/> 31 <Setter Property="Command" Value="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"/> 32 <Setter Property="CommandParameter" Value="{Binding ViewName}"/> 33 </Style> 34 </MenuItem.ItemContainerStyle> 35 </MenuItem> 36 <MenuItem Header="プラグイン" ItemsSource="{Binding PluginMenus}"> 37 <MenuItem.ItemContainerStyle> 38 <Style TargetType="MenuItem"> 39 <Setter Property="Header" Value="{Binding Header}"/> 40 <Setter Property="Command" Value="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"/> 41 <Setter Property="CommandParameter" Value="{Binding ViewName}"/> 42 </Style> 43 </MenuItem.ItemContainerStyle> 44 </MenuItem> 45 </Menu> 46 <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" /> 47 </Grid> 48</Window>

投稿2024/03/18 06:43

sakizakino

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問