'How to replace a registered service with a new one in MVVM?
Trying to migrate from good old MVVM Light to Windows Community Toolkit. How are we supposed to override a registered service, i.e. replace it with another implementation at runtime?
Example
I have got a UI layer which is basically a WPF application and a VM layer which is a class library. The structure looks like this:
App (UI)
MainWindow (window)
WPFDialogService (implements IDialog interface)
Library (VM)
IDialog (interface)
ConsoleDialogService (implements IDialog interface)
MainVM (backend of MainWindow)
ViewModelLocator (static class the performs Ioc registrations)
ViewModelLocator
is central hub and is supposed to register all services. By default, it registers ConsoleDialogService
as the default dialog service.
static ViewModelLocator()
{
services.AddSingleton<IDialogService>(new ConsoleDialogService())
.AddSingleton<MainVM>();
Ioc.Default.ConfigureServices(services.BuildServiceProvider());
}
But the UI layer is supposed to unregister that and inject a different service WPFDialogService
later (in the constructor of MainWindow
or in App
class).
In MVVM Light, we had Register
and Unregister
methods using which we could achieve this easily. However in WCT I do not see any equivalent. Am I supposed to call ConfigureServices()
again? How will it affect other services that are already registered? Is ther a way to replace a single service registration only without affecting others?
Also how do we manage service injection through constructors in WCT?
Solution 1:[1]
OK. I think I have finally found my way. Not sure if this is the standard/recommended approach, but it does solve the problem in an elegant way.
Problem with the standard MVVM Light approach is that the ViewModelLocator
class lives at the VM level, which by definition of MVVM, does not have access to View or Application layers. Therefore the implementations that live at those levels cannot be registered in ViewModelLocator
. As a remedy, MVVM Light provides register
and unregister
facility that we can call in those higher layers to replace existing registrations or introduce new ones.
When migrating/upgrading to Windows Community Toolkit, the whole idea of having a centralized ViewModel Locator must be avoided. Service registration must be performed at the highest level (e.g. Application layer) that has access to all implementations so as to select correct implementation (based on whatever criteria drive that selection). This will avoid the need of replacing/injecting any new registrations at a later stage.
Service injection should be entirely delegated to the service container. Introduce your service dependencies as constructor parameters and let your service container inject them automatically for you. No need of a centralized ViewModel Locator.
This approach can be used not only for the VM layer, but even for your UI components (windows/dialogs/custom control classes etc.). Just register them in your service container, add required dependences to their constructor and then whenever you need to instantiate an object, just call service container's GetService<T>
to let it do all the injection magic for you.
Solution 2:[2]
If you want to replace the service in the collection, you're going to have trouble. You can't simply add/remove services from the serviceCollection at runtime. You will need to create a new scope, rebuild the service provider, etc.
I do have an easier way you can solve this though. What you can do is change properties on an existing service in the collection. How about instead of trying to replace the entire service, you replace properties on a wrapper service instead. Would this be a viable solution for you ?
I have written an example for you, I tried to keep the example inline with your code:
Wrapper classes and dialog services
public interface IDialogService
{
}
public class ConsoleDialogService : IDialogService
{
}
public class WPFDialogService : IDialogService
{
}
public interface IDialogServiceWrapper
{
public IDialogService DialogService { get; set; }
}
public class DialogServiceWrapper : IDialogServiceWrapper
{
public IDialogService DialogService { get; set; }
}
Building the service provider
public static void ViewModelLocator()
{
services.AddSingleton<IDialogServiceWrapper>(new DialogServiceWrapper())
.AddSingleton<MainVM>();
var container = services.BuildServiceProvider();
var dialogWrapper = container.GetService<IDialogServiceWrapper>();
dialogWrapper.DialogService = new ConsoleDialogService();
Ioc.Default.ConfigureServices(services.BuildServiceProvider());
}
UI Layer
public class MyUiLayer
{
public IDialogServiceWrapper _dialogServiceWrapper;
public MyUiLayer(IDialogServiceWrapper dialogServiceWrapper)
{
_dialogServiceWrapper = dialogServiceWrapper;
}
public void SwitchContainer()
{
_dialogServiceWrapper.DialogService = new WPFDialogService();
}
}
Let me know if you need me to clarify anything.
Happy coding!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | dotNET |
Solution 2 | Geoffrey Fook |