简体   繁体   中英

MVVM: View Creates ViewModel ViewModel takes Model as a Constructor argument. Is this an MVVM violation?

In MVVM I was reading that the View and the Model should not know about each other. (C# example)

I have the ViewModel set up such that the ViewModel takes an instance of the Model as an input argument. This is done to support Dependency Injection via the constructor to help with Unit Testing, but I am not using a Dependency Injection Framework.

ViewModel(IModel ModelInstance){}

If the View is responsible for creating the View Model, is the following a violation of MVVM?

View()
{
   Model myModel = new Model ();
   this.DataContext = new ViewModel(myModel);
}

This seems like an MVVM violation because the View knows about the Model, as it is explictly creating the Model.

Is this an MVVM violation? What have folks done to work around this problem?

I have read the other similar MVVM questions and I still wasn't seeing any concrete ways to address this. How should a Model get passed into the ViewModel?

Specifically I'm asking if the View creates the ViewModel, who is responsible for creating the model that gets passed into the ViewModel

I use dependency injection, so the VM gets injected into the view, and the "application logic" classes (aka models) get injected into the VM.

DI is the way to go if possible, but if you can't/don't want to use DI then the simplest option would be for the view to instantiate the VM, and the VM to instantiate the model. This isn't very unit testing friendly though, as you won't be able to "mock" the model.

Another option is to use "factories", so the view would call a (usually) static factory class method that is responsible for instantiating the model, instantiating the VM (passing the model to its ctr), then returning the VM. This approach would support unit testing of the VM, as you would be able to pass a mock model to its ctr.

Edit 1 - in response to first comment When it comes to passing the VM into the view, something, somewhere needs to be responsible for instantiating the view. That mechanism in turn needs to instantiate the VM (that the view is expecting), then instantiate the model that the VM is expecting, and so on up the chain of dependencies. This can get very messy without a DI framework.

Many MVVM frameworks include some kind of "navigation service" that sits on top of the DI framework and abstracts away (and vastly simplifies) much of what we're talking about here. Let's say you click a button to navigate to a different view - the button's ICommand code will request navigation via the navigation service, passing it a view name or perhaps the view's type (depending on the MVVM framework). The framework (in conjunction with the DI framework) will then instantiate the correct view. This will in turn result in the DI framework instantiating any dependencies that the view has (eg the VM), resolve it's dependencies (the model), and so on.

It's hard to provide an example without knowing the architecture of your app, how you navigate between views, and so on. Also, I've always used DI (and 'Prism' for navigation), so I'd probably have a hard time rolling a bespoke solution!

MVVM isn't like a religion where the inquisition drags an unbeliever away to torture and burn at the stake.

If you worked in a team then your code wouldn't get past the first merge request review. But whoever reviewed it would presumably have told you "this is bad" when they rejected it. And told you what accepted practice is.

That hasn't happened so I guess there's just you working on this and you can do what you like.

Having said that.

That code does not follow the MVVM pattern.

The view shouldn't go get some data to pass into a viewmodel. It's the viewmodel's job to adapt and present it with data.

The model isn't a class like in MVC usage, it's whatever provides data. Often a wrapper round a Rest call like httpget.

I recommend viewmodel first wherever practical and a single window application.

The view is then a datatemplate that data from the viewmodel is templated out into.

Like this how to open new WPF window in stack panel in WPF mainwindow?

At it's simplest, mainwindowviewmodel controls what viewmodel is current and that in turn is templated.

You'd want more sophistication in practice.

In this though, the view does not go get it's viewmodel.

Let's differentiate between parent viewmodels and children. Frequently you will have a viewmodel for viewing or editing a bunch of things. See all invoices for a client or some such.

That parent viewmodel needs a bunch of data.

A pattern I have frequently used is to separate out instantiation of that parent viewmodel from fetching data.

The ctor is pretty much empty and there's an async task which will get any required data. You can instantiate and inject services, present your viewmodel so the view starts to render. Then separately and asynchronously get your data. The view appears with it's loading spinner, the task completes and the spinner is collapsed the data shown.

That task can be included in an interface all viewmodels implement so you can then generically instantiate a vm (usually out a di container) and await a GetMyData Task.

In a commercial apps the model is a call to a separate web site - web api or node or whatever.

I recommend separation of classes that model returns from viewmodels. Even if you're "just" using dapper to fill a class. Even if you have entity framework and those entity classes look like maybe you could "just" use them. Think of how you get that data as a separate model with separate dto classes.

Copying property A from one class to another makes for some repetitive tedious code. There are packages will make that easy. I like automapper. You then have a fracture point where you can conveniently insert any translation. Have a string in your model but want a datetime in your viewmodel? No problem you just put a bit of a lambda in the automapper definition.

When you want to commit data, you then do the reverse viewmodel => automapper => model.

When you want to edit a viewmodel automapper has another bonus in that you can deep copy any instance easily. Edit a copy. If it fails valiation then you can bin that copy and your original is pristine.

In this way though.

Any Viewmodel is presented to the UI and templated into controls.

A parent viewmodel gets it's data by calling a service.

That service "just" returns or accepts dto from the VMs perspective. The service hides whatever is beyond it.

Perhaps some prototyp-ish code. Let's assume we have a small app and no web server. There's just a repository returns data.

We have SomeViewModel and SomeViewmodel needs a collection of Somes.

Where does it get them?

Out a service which supplies a List where that Some is a DTO.

Where's it get that service?

That's resolved out dependency injection so it's passed into the ctor. The view knows nothing about any viewmodels, repositories or anything. SomeDataRepository can have an interface so you resolve against ISomeDataRepository and you can switch out moqs if you want to for unit testing. Or some other implementation of the repository if your requirement is very very unusual.

    public SomeViewModel(SomeDataRepository repos) 
    { 

In SomeViewModel there's a list filled using some mapper conversion

    SomeList = MapperConversion<SomeViewModel>(await repos.GetSomeAsync);

The repository Some dto is somewhat loosely coupled with the SomeViewModel since mapper copies property to property.

The constructors are not the problem. They are fine. The View Model, that is usually composed of View Model and Model classes. It therefore has explicit dependencies. The View Model class can either instantiate this dependencies on its own or, to improve extensibility, request them via a constructor (or property).

If you decide to use constructor injection, you must also use a DI container that resolves the dependencies - automatically. If done correctly you don't have to use the new keyword anymore.

You don't want the classes of the View to know any internals of View Model classes. This includes that they shouldn't know the Model classes that the View Model classes depend on.

The MVVM dependency graph

View ---> View Model ---> Model

If you you decide to compose objects using constructors, you usually chose an IoC framework to do it for you. This will also solve the instantiation problem (to prevent the View classes to know any Model classes).

The following example uses the .NET dependency injection library ( Microsoft.Extensions.DependencyInjection namespace).

The key is to start the application manually. This way you have control over the flow and you can configure the dependency contaioner before the application has started:

App.xaml
Register a Application.Startup event handler to manually start the application.

<Application Startup="App_OnStartup">

</Application>

App.xaml
Configure the IoC container of your choice and start the application.

using Microsoft.Extensions.DependencyInjection;

class App : Application
{
  private void App_OnStartup(object sender, StartupEventArgs e)
  {
    ServiceProvider container = new ServiceCollection()
      .AddSingleton<IModel, Model>()
      .AddSingleton<IViewModel, ViewModel>()
      .AddSingleton<MainWindow>()
      .BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true });

    MainWindow mainWindow = container.GetRequiredService<MainWindow>();
    //mainWindow.Closed += ShutdownApplication_OnMainWindowClosed;
    mainWindow.Show();
  }
}

ViewModel.cs

class ViewModel : IViewModel
{
  private IModel Model { get; }

  public ViewModel(IModel model) => this.Model = model;
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private IViewModel MainViewModel { get; }

  public MainWindow(IViewModel dataContext) 
  {
    this.DataContext = dataContext;
    this.MainViewModel = dataContext;
  }
}

Dependency injection in .NET
Tutorial: Use dependency injection in .NET

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM