We're updating an old .Net Framework application to .Net 7 and with it, many of it's dependencies. The application is built on Prism (currently version 6.3.0) and it's corresponding Autofac implementation. Newer versions of prism no longer support Autofac since it transitioned to being an immutable container, so I have tried updating to use Prism's DryIoc implementation, however, after transitioning what should be simple view/viewmodel registrations, (using Reactive UI's IViewFor<TViewModel> format) the viewmodels are no longer resolvable from the container as they were before when using Autofac.
To expand on the reactive UI usage, we link up our views and viewmodels by registering a view type against a view model type wrapped in an IViewFor interface. The view implements this interface then both the view and view model can be resolved from the container. In our Autofac usage, this would look like this:
builder.RegisterType<MainView>().As<IViewFor<MainViewModel>>();
Where the builder is a ContainerBuilder.
After updating to DryIoc, the registation looks like this:
this.Container.Register<IViewFor<MainViewModel>, MainView>()
Before when the Autofac containerBuilder had built a container, the 'MainViewModel' type showed as registered, however in the DryIoc version, it no longer does
I took a step back and created some test projects to prove out the behaviour with different versions of the packages, and it seems to have been narrowed down to switching from Autofac to DryIoc, with the same versions of updated reactive UI (19.2.4) the behaviour is the same, and without updating Prism, but switching to Prism.DryIoc 6.3.0 from Prism.Autofac, the behaviour is also the same.
I assume my registration syntax must be slightly wrong, I have also tried DryIoc's 'RegisterMany' method to try ensure both the view and wrapped viewmodel are registered. Lastly I can simply register the viewmodel directly, however I feel I must be missing something for only switching container technology to lead to such different behaviour within Prism/ReactiveUI interaction, which is why I am asking here.
The actual app is more complex but boils down to this MWE to display the behaviour Using Autofac:
using Autofac;
using Prism.Autofac;
using ReactiveUI;
namespace TestPrism
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestCanGetMainViewModel()
{
var app = new App();
// Passes
Assert.IsTrue(app.IsVmRegistered());
}
internal class App
{
private Bootstrapper bootstrapper;
internal App()
{
bootstrapper = new Bootstrapper();
}
internal bool IsVmRegistered()
{
return bootstrapper.IsVmRegistered();
}
}
internal class Bootstrapper : AutofacBootstrapper
{
internal Bootstrapper()
{
Run();
}
protected override void ConfigureContainerBuilder(ContainerBuilder builder)
{
base.ConfigureContainerBuilder(builder);
builder.RegisterInstance<string>("Hello");
builder.RegisterType<MainView>().As<IViewFor<MainViewModel>>();
}
internal bool IsVmRegistered()
{
return this.Container.IsRegistered<MainViewModel>();
}
}
// Implements IViewFor<TViewModel>
internal class MainView : ReactiveUserControl<MainViewModel> { }
internal class MainViewModel : ReactiveObject, IActivatableViewModel
{
public MainViewModel(string testDependency) { }
public ViewModelActivator Activator => new ViewModelActivator();
}
}
}
Using DryIoc:
using DryIoc;
using Prism.DryIoc;
using ReactiveUI;
namespace TestPrism
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestCanGetMainViewModel()
{
var app = new App();
// Fails
Assert.IsTrue(app.IsVmRegistered());
}
internal class App
{
private Bootstrapper bootstrapper;
internal App()
{
bootstrapper = new Bootstrapper();
}
internal bool IsVmRegistered()
{
return bootstrapper.IsVmRegistered();
}
}
internal class Bootstrapper : DryIocBootstrapper
{
internal Bootstrapper()
{
Run();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
// Register test dependency
this.Container.RegisterInstance<string>("Hello");
this.Container.Register<IViewFor<MainViewModel>, MainView>();
}
internal bool IsVmRegistered()
{
return this.Container.IsRegistered<MainViewModel>();
}
}
// Implements IViewFor<TViewModel>
internal class MainView : ReactiveUserControl<MainViewModel> { }
internal class MainViewModel : ReactiveObject, IActivatableViewModel
{
public MainViewModel(string testDependency) { }
public ViewModelActivator Activator => new ViewModelActivator();
}
}
}
My test project's dependencies:
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Autofac" Version="4.9.4" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Prism.Autofac" Version="6.3.0" />
<PackageReference Include="Prism.DryIoc" Version="6.3.0" />
<PackageReference Include="ReactiveUI.WPF" Version="19.2.1" />
<PackageReference Include="Prism.Wpf" Version="6.3.0" />
I do see the same behaviour with newer versions of prism as well
I assume the containers simply work differently and created a simple method that runs the original registration as well as registering the viewmodel directly:
Which resolved my issue