Force loading assemblies in webassembly project - MEF - AvaloniaUI

41 Views Asked by At

Problem:
When using MEF with webassembly the composition is not working.

My setup:
I have a AvaloniaUI project with ReactiveUI as MVVM Framework. To load modules I have added MEF. I have multiple projects.

  • Module A
  • Module B
  • Shell
  • Startup.Desktop
  • Startup.Browser

The startup projects have a reference to the shell project. In the Shell project the bootstrapping is in the App.xaml.cs, here I initialize my Logger, IoC and the modules.

At the beginning I was using a DirectoryCatalog but there were no modules found when using the browser startup. I switched to ApplicationCatalog and added all modules as reference to the startup projects. With the desktop startup everything works, with the browser it still does not work. I even added stuff like

IModule _ = new ModuleA();
_ = new ModuleB();

to my browser Program at the very beginning to make sure the module assemblies are downloaded.

Browser Program

private static async Task Main(string[] args)
{
    TestLoad();
    var appBuilder = BuildAvaloniaApp();
    var app = appBuilder.Instance as App;
    await appBuilder.WithInterFont()
        .UseReactiveUI()
        .StartBrowserAppAsync("out");
    app?.Dispose();
}

public static AppBuilder BuildAvaloniaApp()
{
    return AppBuilder.Configure<App>();
}

private static void TestLoad()
{
    IModule _ = new ModuleA();
    _ = new ModuleB();
}

Desktop Program

[STAThread]
public static void Main(string[] args)
{
    var appBuilder = BuildAvaloniaApp();
    var app = appBuilder.Instance as App;
    appBuilder.StartWithClassicDesktopLifetime(args);
    app?.Dispose();
}

public static AppBuilder BuildAvaloniaApp()
    => AppBuilder.Configure<App>()
        .UsePlatformDetect()
        .WithInterFont()
        .LogToTrace()
        .UseReactiveUI();

Bootstrapping

public override void OnFrameworkInitializationCompleted()
{
    var programVersion = Assembly.GetExecutingAssembly().GetName().Version;
    _logger = LoggerConfigurationHelper.ConfigureLogger();
    _logger.Info("  ====================  Application Startup  ====================  ");
    _logger.Info($"Current {StringConstants.ProductName} version: {programVersion}");
    _logger.Debug("Registering services");

    var moduleLoader = new ModuleLoader(_logger);

    _host = Host.CreateDefaultBuilder()
        .ConfigureServices((_, services) =>
        {
            services.UseMicrosoftDependencyResolver();
            var resolver = Locator.CurrentMutable;
            resolver.InitializeSplat();
            resolver.InitializeReactiveUI();
            resolver.UseNLogWithWrappingFullLogger();

            services.AddSingleton(typeof(ILogger), _logger);
            services.AddSingleton(typeof(IModuleLoader), moduleLoader);

            services.AddTransient<MainWindowViewModel>();
            services.AddTransient<MainViewModel>();
            Locator.CurrentMutable.Register(() => new MainView(), typeof(IViewFor<MainViewModel>));

            foreach (var module in moduleLoader.Modules)
                module.Value.Register(services);
        })
        .Build();
    _container = _host.Services;

    _logger.Debug("Registering services finished");
    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        desktop.MainWindow = new MainWindow { DataContext = _container.GetService<MainWindowViewModel>() };
    else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
        singleViewPlatform.MainView = new MainView { DataContext = _container.GetService<MainViewModel>() };
    else
    {
        const string error = "Can not build up shell: unknown application lifetime type";
        _logger.Fatal(error);
        throw new Exception(error);
    }

Module loading

[ImportMany(typeof(IModule), AllowRecomposition = false)]
private IEnumerable<Lazy<IModule>>? _modules;

public Dictionary<string, IModule> Modules { get; } = new();

public ModuleLoader(ILogger logger)
{
    _logger = logger;

    var catalog = new ApplicationCatalog();
    var compositionContainer = new CompositionContainer(catalog);
    compositionContainer.ComposeParts(this);
    
    DiscoverModules();
}

private void DiscoverModules()
{
    _logger.Debug("Searching modules");

    if (_modules == null)
    {
        _logger.Error("Modules could not be loaded");
        return;
    }

    _logger.Debug($"{_modules.Count()} modules found");
    foreach (var module in _modules)
    {
        _logger.Debug($"Module found: {module.Value.Name}");
        Modules[module.Value.Name] = module.Value;
    }
    _logger.Debug("Finished discovering modules");
}

If I have a breakpoint in the ModuleLoader constructor I can see that with browser startup the _modules are empty after they should have been injected.

The exception says Unable to resolve service for type ISomeService. This should be injected into the MainViewModel, but does not work because the module could not register it.

Why does MEF fail loading the modules for the webassembly variant?

0

There are 0 best solutions below