What is a ViewModelLocator and what are its pros/cons compared to DataTemplates?

本小妞迷上赌 提交于 2019-11-26 14:58:19

Intro

In MVVM the usual practice is to have the Views find their ViewModels by resolving them from a dependency injection (DI) container. This happens automatically when the container is asked to provide (resolve) an instance of the View class. The container injects the ViewModel into the View by calling a constructor of the View which accepts a ViewModel parameter; this scheme is called inversion of control (IoC).

Benefits of DI

The main benefit here is that the container can be configured at run time with instructions on how to resolve the types that we request from it. This allows for greater testability by instructing it to resolve the types (Views and ViewModels) we use when our application actually runs, but instructing it differently when running the unit tests for the application. In the latter case the application will not even have a UI (it's not running; just the tests are) so the container will resolve mocks in place of the "normal" types used when the application runs.

Problems stemming from DI

So far we have seen that the DI approach allows easy testability for the application by adding an abstraction layer over the creation of application components. There is one problem with this approach: it doesn't play well with visual designers such as Microsoft Expression Blend.

The problem is that in both normal application runs and unit test runs, someone has to set up the container with instructions on what types to resolve; additionally, someone has to ask the container to resolve the Views so that the ViewModels can be injected into them.

However, in design time there is no code of ours running. The designer attempts to use reflection to create instances of our Views, which means that:

  • If the View constructor requires a ViewModel instance the designer won't be able to instantiate the View at all -- it will error out in some controlled manner
  • If the View has a parameterless constructor the View will be instantiated, but its DataContext will be null so we 'll get an "empty" view in the designer -- which is not very useful

Enter ViewModelLocator

The ViewModelLocator is an additional abstraction used like this:

  • The View itself instantiates a ViewModelLocator as part of its resources and databinds its DataContext to the ViewModel property of the locator
  • The locator somehow detects if we are in design mode
  • If not in design mode, the locator returns a ViewModel that it resolves from the DI container, as explained above
  • If in design mode, the locator returns a fixed "dummy" ViewModel using its own logic (remember: there is no container in design time!); this ViewModel typically comes prepopulated with dummy data

Of course this means that the View must have a parameterless constructor to begin with (otherwise the designer won't be able to instantiate it).

Summary

ViewModelLocator is an idiom that lets you keep the benefits of DI in your MVVM application while also allowing your code to play well with visual designers. This is sometimes called the "blendability" of your application (referring to Expression Blend).

After digesting the above, see a practical example here.

Finally, using data templates is not an alternative to using ViewModelLocator, but an alternative to using explicit View/ViewModel pairs for parts of your UI. Often you may find that there's no need to define a View for a ViewModel because you can use a data template instead.

BrunoLM

An example implementation of @Jon's answer

I have a view model locator class. Each property is going to be an instance of the view model that I'm going to allocate on my view. I can check if the code is running in design mode or not using DesignerProperties.GetIsInDesignMode. This allows me to use a mock model during designing time and the real object when I'm running the application.

public class ViewModelLocator
{
    private DependencyObject dummy = new DependencyObject();

    public IMainViewModel MainViewModel
    {
        get
        {
            if (IsInDesignMode())
            {
                return new MockMainViewModel();
            }

            return MyIoC.Container.GetExportedValue<IMainViewModel>();
        }
    }

    // returns true if editing .xaml file in VS for example
    private bool IsInDesignMode()
    {
        return DesignerProperties.GetIsInDesignMode(dummy);
    }
}

And to use it I can add my locator to App.xaml resources:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

<Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>

And then to wire up your view (ex: MainView.xaml) to your viewmodel:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
Hristo Yankov

I don't understand why the other answers of this question wrap around the Designer.

The purpose of the View Model Locator is to allow your View to instantiate this (yes, View Model Locator = View First):

public void MyWindowViewModel(IService someService)
{
}

instead of just this:

public void MyWindowViewModel()
{
}

by declaring this:

DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

Where ViewModelLocator is class, which references a IoC and that's how it solves the MainWindowModel property it exposes.

It has nothing to do with providing Mock view models to your view. If you want that, just do

d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"

The View Model Locator is a wrapper around some (any) Inversion of Control container, such as Unity for example.

Refer to:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!