Our application is similar to an IDE. The user can open files (coming from a DB), and then use the application's various aspects to edit that file. The application can have multiple tabs with different files open. We use Simple Injector with WPF and MVVM. Bootstrapping the initial application works fine and can be resolved from the container. However, we're not sure on the DI design that handles the runtime data (the file to be opened). We essentially have something like this:
public class MainWindowVm : ObservableRecepient
{
public MainWindowVm(IQueryProcessor queryProcessor, IDependency dep){ /*...*/}
public ObservableCollection<FileVm> OpenFiles { get; } = new();
// Called from a message dispatched from another component.
public void OpenFile(FileDomainModel model)
{
// Oh no, FileVm should not be manually new'ed
var fileVm = new FileVm(model, queryProcessor, dep);
// Alternative?
var fileVm = // Where could FileVm come from?
fileVm.Model = model; // Property Injection
fileVm.SetModel(model); // or Method injection
OpenFiles.Add(fileVm);
//...
}
}
There are obviously more injectable dependencies in our real application.
Now, I have extensively searched, and most proposed solutions have serious drawbacks, such as injecting the container outside the composition root or adding a service locator. Also, the model can't really "flow" exclusively via methods through the applications — the view models are bound to views and as such have properties the views bind to.
One possible solution we're eying is using the messenger system of the MVVM toolkit. The view models can get instantiated by the container (good), and then receive a message when a file is opened to set their properties for binding internally (...good?). Still, the question remains how new view models are instantiated at runtime, since every time a file is opened, we need a new set of VMs.
If the answer is "you need to change your design", this is also valid and I am open to suggestions.
In the MVVM model I'm currently working on (WPF, Caliburn Micro, Simple Injector) we hide the management, creation and opening of screens behind an abstraction.
In your example, you show the use of an IQueryProcessor
abstraction. I assume this abstraction is similar to what I described years ago here. Our application uses this abstraction extensively. For opening screens, we use an abstraction that is very similar to IQueryProcessor
, i.e. the opening of screens is done through messages:
IQueryProcessor
in your question.I won't be very specific here, because I think giving any concrete examples of the implementation in our application would only push you into a certain direction, while I think it's good to construct these abstractions and implementations from the needs of your specific application.
With a construct like this, however, you would be able to reduce the MainWindowVm
to something like this:
public class MainWindowVm : ObservableRecepient
{
public MainWindowVm(
IQueryProcessor queries,
IModelDialogDispatcher dialogs, // The new abstraction
IDependency dep){ /*...*/}
public ObservableCollection<string> OpenFiles { get; } = new();
public void OpenFile(FileDomainModel model)
{
var message = new SelectFile(model); // the request message
SelectFileResponse response = dialogs.Open(message);
OpenFiles.AddRange(response.SelectedFiles);
}
}
But that said, this message-based architecture works very well in our case to disconnect view models from each other and allow the DI infrastructure to create view models.