Co to jest ViewModelLocator i jakie są jego zalety / wady w porównaniu z DataTemplates?

112

Czy ktoś może mi dać krótkie podsumowanie tego, czym jest ViewModelLocator, jak działa i jakie są zalety / wady korzystania z niego w porównaniu z DataTemplates?

Próbowałem znaleźć informacje w Google, ale wydaje się, że jest wiele różnych ich implementacji i nie ma listy szczegółów, co to jest i wady / zalety korzystania z niego.

Rachel
źródło

Odpowiedzi:

204

Intro

W MVVM zwykłą praktyką jest, aby widoki znajdowały swoje ViewModels, rozwiązując je z kontenera iniekcji zależności (DI). Dzieje się to automatycznie, gdy kontener jest proszony o dostarczenie (rozwiązanie) wystąpienia klasy View. Kontener wstrzykuje ViewModel do widoku, wywołując konstruktora widoku, który akceptuje parametr ViewModel; schemat ten nazywany jest odwróceniem kontroli (IoC).

Korzyści z DI

Główną zaletą jest to, że kontener można skonfigurować w czasie wykonywania za pomocą instrukcji dotyczących rozwiązywania typów, których od niego żądamy. Pozwala to na większą testowalność, instruując go, aby rozwiązać typy (widoki i modele widoków), których używamy, gdy nasza aplikacja faktycznie działa, ale instruując ją inaczej podczas wykonywania testów jednostkowych dla aplikacji. W tym drugim przypadku aplikacja nie będzie miała nawet interfejsu użytkownika (nie jest uruchomiona; tylko testy są), więc kontener będzie rozwiązywać makiety zamiast „normalnych” typów używanych podczas uruchamiania aplikacji.

Problemy wynikające z DI

Jak dotąd widzieliśmy, że podejście DI umożliwia łatwe testowanie aplikacji poprzez dodanie warstwy abstrakcji nad tworzeniem komponentów aplikacji. Jest jeden problem z tym podejściem: nie działa dobrze z projektantami wizualnymi, takimi jak Microsoft Expression Blend.

Problem polega na tym, że zarówno w przypadku normalnych uruchomień aplikacji, jak i testów jednostkowych, ktoś musi skonfigurować kontener z instrukcjami, jakie typy należy rozwiązać; dodatkowo ktoś musi poprosić kontener o rozpoznanie widoków, aby można było do nich wstrzyknąć ViewModels.

Jednak w czasie projektowania nasz kod nie działa . Projektant stara się wykorzystać odbicie do tworzenia instancji naszych Widoków, co oznacza, że:

  • Jeśli konstruktor widoku wymaga wystąpienia ViewModel, projektant nie będzie w stanie w ogóle utworzyć wystąpienia widoku - wystąpi błąd w kontrolowany sposób
  • Jeśli View ma konstruktor bez parametrów, wystąpi View, ale tak DataContextbędzie, nullwięc w projektancie otrzymamy "pusty" widok - co nie jest zbyt przydatne

Wprowadź ViewModelLocator

ViewModelLocator to dodatkowa abstrakcja używana w następujący sposób:

  • Widok sam tworzy wystąpienie ViewModelLocator jako część jego zasobów i łączy dane DataContext z właściwością ViewModel lokalizatora
  • Lokalizator w jakiś sposób wykrywa, czy jesteśmy w trybie projektowania
  • Jeśli nie jest w trybie projektowania, lokalizator zwraca ViewModel, który jest rozpoznawany z kontenera DI, jak wyjaśniono powyżej
  • W trybie projektowania lokalizator zwraca ustalony „fikcyjny” ViewModel przy użyciu własnej logiki (pamiętaj: w czasie projektowania nie ma kontenera!); ten ViewModel zazwyczaj jest wstępnie wypełniony danymi fikcyjnymi

Oczywiście oznacza to, że widok musi mieć na początku konstruktora bez parametrów (w przeciwnym razie projektant nie będzie mógł go utworzyć).

Podsumowanie

ViewModelLocator to idiom, który pozwala zachować zalety DI w aplikacji MVVM, jednocześnie umożliwiając dobrą współpracę kodu z projektantami wizualnymi. Nazywa się to czasem „mieszalnością” aplikacji (w odniesieniu do Expression Blend).

Po przetrawieniu powyższego zobacz praktyczny przykład tutaj .

Wreszcie, używanie szablonów danych nie jest alternatywą dla używania ViewModelLocator, ale alternatywą dla używania jawnych par View / ViewModel dla części interfejsu użytkownika. Często może się okazać, że nie ma potrzeby definiowania widoku dla ViewModel, ponieważ zamiast tego można użyć szablonu danych.

Jon
źródło
4
+1 za świetne wyjaśnienie. Czy możesz szerzej rozwinąć widok i jego zasoby? Czy masz na myśli właściwości widoku? Lub? Czy masz link z konkretnym przykładem do tego wzoru?
Metro Smurf,
@MetroSmurf: Twój link znajduje się w sekcji Podsumowanie.
Jon
1
Dzięki. Czy są jakieś ograniczenia w używaniu ViewModelLocator? Miałem pewne obawy co do tego, że odwoływał się do statycznego zasobu - czy ViewModels można tworzyć dynamicznie w czasie wykonywania? I czy jest dużo dodatkowego kodu związanego z podłączeniem jednego?
Rachel,
@Rachel: Ten sam link w podsumowaniu powinien odpowiedzieć na te pytania z praktycznymi przykładami.
Jon
2
Niezwykle myląca odpowiedź. Głównym celem narzędzia View Model Locator nie jest dostarczanie projektantowi fikcyjnych danych. Możesz to łatwo zrobić, określając d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}". Celem lokalizatora jest faktyczne włączenie DI w widokach, ponieważ WPF tak źle go udostępnia. Przykład: masz główne okno, które otwiera jakieś okno dialogowe. Aby rozwiązać DI w oknie dialogowym w zwykły sposób, musisz przekazać go jako zależność od okna głównego! Można tego uniknąć dzięki narzędziu View Locator.
hyankov
10

Przykładowa implementacja odpowiedzi @ Jona

Mam klasę lokalizatora modelu widoku. Każda właściwość będzie instancją modelu widoku, który zamierzam zaalokować w moim widoku. Mogę sprawdzić, czy kod działa w trybie projektowania, czy nie DesignerProperties.GetIsInDesignMode. Dzięki temu mogę używać makiety podczas projektowania i rzeczywistego obiektu podczas uruchamiania aplikacji.

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);
    }
}

Aby z niego skorzystać, mogę dodać mój lokalizator do App.xamlzasobów:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

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

A następnie, aby połączyć widok (np. MainView.xaml) z modelem widoku:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
BrunoLM
źródło
czy jest jakaś różnica w używaniu thiszamiast dummy?
Sebastian Xawery Wiśniowiecki
5

Nie rozumiem, dlaczego inne odpowiedzi na to pytanie otaczają Projektanta.

Celem narzędzia View Model Locator jest umożliwienie Twojemu Viewowi utworzenia tego wystąpienia (tak, View Model Locator = View First):

public void MyWindowViewModel(IService someService)
{
}

zamiast tego:

public void MyWindowViewModel()
{
}

oświadczając, że:

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

Gdzie ViewModelLocatorjest klasa, która odwołuje się do IoC i tak rozwiązuje MainWindowModeleksponowaną właściwość.

Nie ma to nic wspólnego z udostępnianiem modeli widoku Mock do widoku. Jeśli tego chcesz, po prostu zrób

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

View Model Locator jest opakowaniem wokół pewnego (dowolnego) kontenera Inversion of Control, takiego jak na przykład Unity.

Odnosić się do:

hyankov
źródło
Lokalizator modelu widoku nie wymaga kontenera, użytkownik decyduje, w jaki sposób model widoku zostanie rozwiązany poprzez konfigurację i możesz użyć kontenera lub po prostu samodzielnie utworzyć typ. Możesz więc na przykład przeprowadzić lokalizację modelu widoku na podstawie konwencji, zamiast wstępnie rejestrować wszystkie widoki i wyświetlać modele w jakimś kontenerze.
Chris Bordeman,
Masz rację mówiąc „ Nie rozumiem, dlaczego inne odpowiedzi [...] zawijają się wokół Projektanta ” +1, ale celem lokalizatora jest usunięcie z widoku wszelkiej wiedzy o tym, jaki jest model widoku utworzony, uniezależniając widok od tej instancji, pozostawiając to lokalizatorowi. Lokalizator będzie w stanie zapewnić różne smaki modelu widoku, być może niektóre niestandardowe dodane przez wtyczki, którymi będzie zarządzał lokalizator (i konkretny na czas projektowania). Widok będzie wolny od wszelkich procesów lokalizowania poprawnej wersji modelu widoku, co jest rzeczywiście dobre dla SoC.
min