Skip to content

Commit 66544e6

Browse files
committed
Migrate DI from Microsoft.VisualStudio.Composition to Microsoft.Extensions.DependencyInjection
1 parent 21e5d0f commit 66544e6

File tree

6 files changed

+103
-200
lines changed

6 files changed

+103
-200
lines changed

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />
2424
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
2525
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
26+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
2627
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
2728
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
2829
<PackageVersion Include="Microsoft.NETCore.ILAsm" Version="8.0.0" />
2930
<PackageVersion Include="Microsoft.NETCore.ILDAsm" Version="8.0.0" />
3031
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
31-
<PackageVersion Include="Microsoft.VisualStudio.Composition" Version="17.11.13" />
3232
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
3333
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
3434
<PackageVersion Include="NaturalSort.Extension" Version="4.3.0" />
@@ -45,6 +45,7 @@
4545
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.1" />
4646
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0" />
4747
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
48+
<PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.20.0" />
4849
<PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.20.0" />
4950
<PackageVersion Include="TomsToolbox.Wpf.Composition.Mef" Version="2.20.0" />
5051
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.20.0" />

ILSpy/App.xaml.cs

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using System.Linq;
2424
using System.Reflection;
2525
using System.Runtime.Loader;
26-
using System.Text;
2726
using System.Threading.Tasks;
2827
using System.Windows;
2928
using System.Windows.Documents;
@@ -36,8 +35,6 @@
3635

3736
using Medo.Application;
3837

39-
using Microsoft.VisualStudio.Composition;
40-
4138
using TomsToolbox.Wpf.Styles;
4239
using ICSharpCode.ILSpyX.TreeView;
4340

@@ -47,6 +44,11 @@
4744
using System.Globalization;
4845
using System.Threading;
4946

47+
using Microsoft.Extensions.DependencyInjection;
48+
49+
using TomsToolbox.Composition.MicrosoftExtensions;
50+
using TomsToolbox.Essentials;
51+
5052
namespace ICSharpCode.ILSpy
5153
{
5254
/// <summary>
@@ -59,10 +61,9 @@ public partial class App : Application
5961

6062
public static IExportProvider ExportProvider { get; private set; }
6163

62-
internal class ExceptionData
64+
internal record ExceptionData(Exception Exception)
6365
{
64-
public Exception Exception;
65-
public string PluginName;
66+
public string PluginName { get; init; }
6667
}
6768

6869
public App()
@@ -80,6 +81,14 @@ public App()
8081

8182
InitializeComponent();
8283

84+
if (!InitializeDependencyInjection(SettingsService.Instance))
85+
{
86+
// There is something completely wrong with DI, probably some service registration is missing => nothing we can do to recover, so stop and shut down.
87+
Exit += (_, _) => MessageBox.Show(StartupExceptions.FormatExceptions(), "Sorry we crashed!");
88+
Shutdown(1);
89+
return;
90+
}
91+
8392
if (!Debugger.IsAttached)
8493
{
8594
AppDomain.CurrentDomain.UnhandledException += ShowErrorBox;
@@ -92,8 +101,6 @@ public App()
92101

93102
Resources.RegisterDefaultStyles();
94103

95-
InitializeMef().GetAwaiter().GetResult();
96-
97104
// Register the export provider so that it can be accessed from WPF/XAML components.
98105
ExportProviderLocator.Register(ExportProvider);
99106
// Add data templates registered via MEF.
@@ -103,7 +110,7 @@ public App()
103110
ThemeManager.Current.Theme = sessionSettings.Theme;
104111
if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture))
105112
{
106-
Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture);
113+
Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new(sessionSettings.CurrentCulture);
107114
}
108115

109116
EventManager.RegisterClassHandler(typeof(Window),
@@ -125,6 +132,13 @@ public App()
125132
SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists();
126133
}
127134

135+
public new static App Current => (App)Application.Current;
136+
137+
public new MainWindow MainWindow {
138+
get => (MainWindow)base.MainWindow;
139+
set => base.MainWindow = value;
140+
}
141+
128142
private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue<AssemblyTreeModel>().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions();
129143

130144
static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName)
@@ -136,22 +150,17 @@ static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyN
136150
return context.LoadFromAssemblyPath(assemblyFileName);
137151
}
138152

139-
private static async Task InitializeMef()
153+
private static bool InitializeDependencyInjection(SettingsService settingsService)
140154
{
141155
// Add custom logic for resolution of dependencies.
142156
// This necessary because the AssemblyLoadContext.LoadFromAssemblyPath and related methods,
143157
// do not automatically load dependencies.
144158
AssemblyLoadContext.Default.Resolving += ResolvePluginDependencies;
145-
// Cannot show MessageBox here, because WPF would crash with a XamlParseException
146-
// Remember and show exceptions in text output, once MainWindow is properly initialized
147159
try
148160
{
149-
// Set up VS MEF. For now, only do MEF1 part discovery, since that was in use before.
150-
// To support both MEF1 and MEF2 parts, just change this to:
151-
// var discovery = PartDiscovery.Combine(new AttributedPartDiscoveryV1(Resolver.DefaultInstance),
152-
// new AttributedPartDiscovery(Resolver.DefaultInstance));
153-
var discovery = new AttributedPartDiscoveryV1(Resolver.DefaultInstance);
154-
var catalog = ComposableCatalog.Create(Resolver.DefaultInstance);
161+
var services = new ServiceCollection();
162+
163+
155164
var pluginDir = Path.GetDirectoryName(typeof(App).Module.FullyQualifiedName);
156165
if (pluginDir != null)
157166
{
@@ -160,62 +169,47 @@ private static async Task InitializeMef()
160169
var name = Path.GetFileNameWithoutExtension(plugin);
161170
try
162171
{
163-
var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(plugin);
164-
var parts = await discovery.CreatePartsAsync(asm);
165-
catalog = catalog.AddParts(parts);
172+
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(plugin);
173+
services.BindExports(assembly);
166174
}
167175
catch (Exception ex)
168176
{
169-
StartupExceptions.Add(new ExceptionData { Exception = ex, PluginName = name });
177+
// Cannot show MessageBox here, because WPF would crash with a XamlParseException
178+
// Remember and show exceptions in text output, once MainWindow is properly initialized
179+
StartupExceptions.Add(new(ex) { PluginName = name });
170180
}
171181
}
172182
}
183+
173184
// Add the built-in parts: First, from ILSpyX
174-
var xParts = await discovery.CreatePartsAsync(typeof(IAnalyzer).Assembly);
175-
catalog = catalog.AddParts(xParts);
185+
services.BindExports(typeof(IAnalyzer).Assembly);
176186
// Then from ILSpy itself
177-
var createdParts = await discovery.CreatePartsAsync(Assembly.GetExecutingAssembly());
178-
catalog = catalog.AddParts(createdParts);
179-
180-
// If/When the project switches to .NET Standard/Core, this will be needed to allow metadata interfaces (as opposed
181-
// to metadata classes). When running on .NET Framework, it's automatic.
182-
// catalog.WithDesktopSupport();
183-
// If/When any part needs to import ICompositionService, this will be needed:
184-
// catalog.WithCompositionService();
185-
var config = CompositionConfiguration.Create(catalog);
186-
var exportProviderFactory = config.CreateExportProviderFactory();
187-
ExportProvider = new ExportProviderAdapter(exportProviderFactory.CreateExportProvider());
188-
189-
// This throws exceptions for composition failures. Alternatively, the configuration's CompositionErrors property
190-
// could be used to log the errors directly. Used at the end so that it does not prevent the export provider setup.
191-
config.ThrowOnErrors();
192-
}
193-
catch (CompositionFailedException ex) when (ex.InnerException is AggregateException agex)
194-
{
195-
foreach (var inner in agex.InnerExceptions)
196-
{
197-
StartupExceptions.Add(new ExceptionData { Exception = inner });
198-
}
187+
services.BindExports(Assembly.GetExecutingAssembly());
188+
// Add the settings service
189+
services.AddSingleton(settingsService);
190+
191+
var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
192+
193+
ExportProvider = new ExportProviderAdapter(serviceProvider);
194+
195+
return true;
199196
}
200197
catch (Exception ex)
201198
{
202-
StartupExceptions.Add(new ExceptionData { Exception = ex });
199+
if (ex is AggregateException aggregate)
200+
StartupExceptions.AddRange(aggregate.InnerExceptions.Select(item => new ExceptionData(ex)));
201+
else
202+
StartupExceptions.Add(new(ex));
203+
204+
return false;
203205
}
204206
}
205207

206208
protected override void OnStartup(StartupEventArgs e)
207209
{
208210
base.OnStartup(e);
209211

210-
var output = new StringBuilder();
211-
212-
if (StartupExceptions.FormatExceptions(output))
213-
{
214-
MessageBox.Show(output.ToString(), "Sorry we crashed!");
215-
Environment.Exit(1);
216-
}
217-
218-
MainWindow = new MainWindow();
212+
MainWindow = new();
219213
MainWindow.Show();
220214
}
221215

ILSpy/AssemblyTree/AssemblyTreeModel.cs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@
2121
using System.Collections.Specialized;
2222
using System.ComponentModel;
2323
using System.ComponentModel.Composition;
24+
using System.Diagnostics.CodeAnalysis;
2425
using System.IO;
2526
using System.Linq;
2627
using System.Reflection.Metadata;
2728
using System.Reflection.Metadata.Ecma335;
28-
using System.Text;
2929
using System.Threading.Tasks;
3030
using System.Windows;
3131
using System.Windows.Input;
3232
using System.Windows.Navigation;
3333
using System.Windows.Threading;
3434

35-
using ICSharpCode.Decompiler;
3635
using ICSharpCode.Decompiler.Documentation;
3736
using ICSharpCode.Decompiler.Metadata;
3837
using ICSharpCode.Decompiler.TypeSystem;
@@ -160,13 +159,15 @@ private bool HandleCommandLineArguments(CommandLineArguments args)
160159
/// Called on startup or when passed arguments via WndProc from a second instance.
161160
/// In the format case, spySettings is non-null; in the latter it is null.
162161
/// </summary>
163-
private void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider? spySettings = null)
162+
private async Task HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider? spySettings = null)
164163
{
165164
var sessionSettings = SettingsService.Instance.SessionSettings;
166165

167166
var relevantAssemblies = commandLineLoadedAssemblies.ToList();
168167
commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore
169-
NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies);
168+
169+
await NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies);
170+
170171
if (args.Search != null)
171172
{
172173
var searchPane = App.ExportProvider.GetExportedValue<SearchPaneModel>();
@@ -180,7 +181,7 @@ public async Task HandleSingleInstanceCommandLineArguments(string[] args)
180181
{
181182
var cmdArgs = CommandLineArguments.Create(args);
182183

183-
await Dispatcher.InvokeAsync(() => {
184+
await Dispatcher.InvokeAsync(async () => {
184185

185186
if (!HandleCommandLineArguments(cmdArgs))
186187
return;
@@ -192,11 +193,11 @@ await Dispatcher.InvokeAsync(() => {
192193
window.WindowState = WindowState.Normal;
193194
}
194195

195-
HandleCommandLineArgumentsAfterShowList(cmdArgs);
196+
await HandleCommandLineArgumentsAfterShowList(cmdArgs);
196197
});
197198
}
198199

199-
private async void NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, ISettingsProvider? spySettings, List<LoadedAssembly> relevantAssemblies)
200+
private async Task NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, ISettingsProvider? spySettings, List<LoadedAssembly> relevantAssemblies)
200201
{
201202
var initialSelection = SelectedItem;
202203
if (navigateTo != null)
@@ -386,26 +387,31 @@ public void Initialize()
386387
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies);
387388
}
388389

389-
private void OpenAssemblies()
390+
private async Task OpenAssemblies()
390391
{
391-
HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings);
392+
await HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings);
392393

393-
AvalonEditTextOutput output = new();
394-
if (FormatExceptions(App.StartupExceptions.ToArray(), output))
394+
if (FormatExceptions(App.StartupExceptions.ToArray(), out var output))
395395
{
396+
output.Title = "Startup errors";
397+
398+
DockWorkspace.Instance.AddTabPage();
396399
DockWorkspace.Instance.ShowText(output);
397400
}
398401
}
399402

400-
private static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output)
403+
private static bool FormatExceptions(App.ExceptionData[] exceptions, [NotNullWhen(true)] out AvalonEditTextOutput? output)
401404
{
402-
var stringBuilder = new StringBuilder();
403-
var result = exceptions.FormatExceptions(stringBuilder);
404-
if (result)
405-
{
406-
output.Write(stringBuilder.ToString());
407-
}
408-
return result;
405+
output = null;
406+
407+
var result = exceptions.FormatExceptions();
408+
if (result.IsNullOrEmpty())
409+
return false;
410+
411+
output = new();
412+
output.Write(result);
413+
return true;
414+
409415
}
410416

411417
private void ShowAssemblyList(string name)

0 commit comments

Comments
 (0)