WPF приложение на MVVM с использованием PRISM 6 и Autofac

создано: 28.02.2017 | опубликовано: 28.02.2017 | обновлено: 29.06.2017 | просмотров: 1264 | комментариев: 2 | WPF и Silverlight

В этой статье показан пример создания WPF-приложения на основе MVVM паттерна проектирования. За основу используется PRISM 6, как MVVM-фреймворк и Autofac, как DI-контейнер.

Создание проекта в Visual Studio

Постараюсь очень кратко изложить основную мысль, поэтому сразу к делу. Для проекта выбираем WPF Application:

Название для проекта вы можете выбрать на своё усмотрение, а я всегда придерживаюсь принципов описанных в предыдущей статье. После того, как Visual Studio создат проект, я удалю файл MainWindow.xaml, для чистоты эксперимента.

Установка Nuget-пакетов

Для построения приложения по принципам MVVM мне потребуется фреймворк, который реализует этот принцип. Причем сам фреймворк может быть написан собственноручно или создан сторонними разработчиками. Я возьму PRISM ​фреймворк, который был разработан Microsoft, но в дальнейшем был отдан в OpenSource другим разработчикам, которые, надо сказать, честно, продолжают наращивать функционал.

Установливаем PRISM:

PM> Install-Package Prism.Wpf -Version 6.2.0 
Attempting to gather dependency information for package 'Prism.Wpf.6.2.0' with respect to project 'WpfApplication1', targeting '.NETFramework,Version=v4.5.2'
Attempting to resolve dependencies for package 'Prism.Wpf.6.2.0' with DependencyBehavior 'Lowest'
Resolving actions to install package 'Prism.Wpf.6.2.0'
Resolved actions to install package 'Prism.Wpf.6.2.0'
Adding package 'CommonServiceLocator.1.3.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'CommonServiceLocator.1.3.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'CommonServiceLocator.1.3.0' to 'packages.config'
Successfully installed 'CommonServiceLocator 1.3.0' to WpfApplication1
Adding package 'Prism.Core.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Core.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Core.6.2.0' to 'packages.config'
Successfully installed 'Prism.Core 6.2.0' to WpfApplication1
Adding package 'Prism.Wpf.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Wpf.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Wpf.6.2.0' to 'packages.config'
Successfully installed 'Prism.Wpf 6.2.0' to WpfApplication1
PM> 

Следующий на очереди DI-контейнер. В моем случае, это Autofac. Я сразу установлю Prism.Autofac, который установит как зависимость сам Autofac:

PM> Install-Package Prism.Autofac
Attempting to gather dependency information for package 'Prism.Autofac.6.2.0' with respect to project 'WpfApplication1', targeting '.NETFramework,Version=v4.5.2'
Attempting to resolve dependencies for package 'Prism.Autofac.6.2.0' with DependencyBehavior 'Lowest'
Resolving actions to install package 'Prism.Autofac.6.2.0'
Resolved actions to install package 'Prism.Autofac.6.2.0'
Adding package 'Autofac.3.5.2' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Autofac.3.5.2' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Autofac.3.5.2' to 'packages.config'
Successfully installed 'Autofac 3.5.2' to WpfApplication1
Adding package 'Prism.Autofac.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Autofac.6.2.0' to folder 'C:\Projects\_Temp\WpfApplication1\packages'
Added package 'Prism.Autofac.6.2.0' to 'packages.config'
Successfully installed 'Prism.Autofac 6.2.0' to WpfApplication1
PM> 

Главное окно и его ViewModel

Теперь пришло время создать новое главное окно программы. Так повелось, что для WPF-приложения я обычно использую название для главного окна Shell. Но перед этим создаем папки Views и ViewModels. Теперь создаем Shell.xaml и ShellViewModel.cs в соответствующих папках.

Bootstrapper на Autofac

Создаем Bootstrapper, который наследуем от AutofacBootstrapper:

/// <summary>
/// Autofac Bootstrapper 
/// </summary>
public class Bootstrapper : AutofacBootstrapper {

    /// <summary>Creates the shell or main window of the application.</summary>
    /// <returns>The shell of the application.</returns>
    /// <remarks>
    /// If the returned instance is a <see cref="T:System.Windows.DependencyObject" />, the
    /// <see cref="T:Prism.Bootstrapper" /> will attach the default <see cref="T:Prism.Regions.IRegionManager" /> of
    /// the application in its <see cref="F:Prism.Regions.RegionManager.RegionManagerProperty" /> attached property
    /// in order to be able to add regions by using the <see cref="F:Prism.Regions.RegionManager.RegionNameProperty" />
    /// attached property from XAML.
    /// </remarks>
    protected override DependencyObject CreateShell() {
        return Container.Resolve<Shell>();
    }

    /// <summary>Initializes the shell.</summary>
    protected override void InitializeShell() {
        base.InitializeShell();
        Application.Current.MainWindow = (Window)Shell;
        Application.Current.MainWindow.Show();
    }

    /// <summary>
    /// Creates the <see cref="T:Autofac.ContainerBuilder" /> that will be used to create the default container.
    /// </summary>
    /// <returns>A new instance of <see cref="T:Autofac.ContainerBuilder" />.</returns>
    protected override ContainerBuilder CreateContainerBuilder() {
        var builder = new ContainerBuilder();

        // регистрация зависимостей в контейнере
        // должны быть здесь...

        return builder;
    }
}

Не трудно заметить, что весь код состоит из переопределения перегрузов методов, где подставлются нужные нам зависимости.

App.xaml

Так как теперь для старта нашего приложения будет использоваться Bootstrapper, необходимо удалить "точку входа" из файла App.xaml.

Удалите выделенный на картинке атрибут.

ShellViewModel.cs

Чтобы наш ShellViewModel превратился в настоящий ViewModel для нашего представления, надо унаследовать класс от BindableBase, который являет собой реализацию интерфейса INotifyPropertyChanged. А еще добавил свойство DisplayName, которое буду использовать как заголовок для моего приложения.

/// <summary>
/// Shell ViewModel
/// </summary>
public class ShellViewModel : BindableBase {

    #region property DisplayName

    /// <summary>
    /// Represent DisplayName property
    /// </summary>
    public string DisplayName {
        get { return _displayName; }
        set { SetProperty(ref _displayName, value); }
    }

    /// <summary>
    /// Backing field for property DisplayName
    /// </summary>
    private string _displayName = "WPF PRISM (MVVM) + DI (Autofac)";

    #endregion

}

Shell.xaml

Пришло время заняться представлением Shell. Для того, что наш ShellViewModel стал объектом DataContext для нашего Shell можно использовать несколько операций. Но для начала откройте разметку Shell.xaml и свяжите свойство Title со свойством DisplayName (из ShellViewModel).

<Window
    x:Class="WpfApplication1.Views.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="{Binding DisplayName}"
    Height="480"
    Width="640"
    ResizeMode="CanMinimize"
    WindowStartupLocation="CenterScreen">
    <Grid></Grid>
</Window>

Теперь можно как вариант в Code-Behind фале App.xaml.cs прописать следующий код

/// <summary>
/// Interaction logic for Shell.xaml
/// </summary>
public partial class Shell : Window {
    public Shell() {
        InitializeComponent();
        DataContext = new ShellViewModel();
    }
}

Можно запустить приложение и мы увидим заголовок главного окна программ будет "WPF PRISM (MVVM) + DI (Autofac)". Но перед тем как запустить надо инициализировать наш Bootstrapper в App.xaml.cs.

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application {
    /// <summary>Raises the <see cref="E:System.Windows.Application.Startup" /> event.</summary>
    /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the event data.</param>
    protected override void OnStartup(StartupEventArgs e) {
        base.OnStartup(e);
        var bootstapper = new Bootstrapper();
        bootstapper.Run();
    }
}

Вот теперь можно запускать

Не очень информативное представление но факт остается в следующем: MVVM - работает! Однако, надо сказать, что PRISM предоставляет более эстетичный вариант привязки View и ViewModel. Удалите из файла Shell.xaml.cs строку

DataContext = new ShellViewModel();

И закройте его.

Более того, я бы вам рекомендовал его больше никогда не открывать при использовании MVVM подхода.

Давайте применим другой подход. Для этого содержимое файла Shell.xaml в соответствии с приведенным листингом.

<Window
    x:Class="WpfApplication1.Views.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True"
    Title="{Binding DisplayName}"
    Height="480"
    Width="640"
    ResizeMode="CanMinimize"
    WindowStartupLocation="CenterScreen">
    <Grid></Grid>
</Window>

Можно запустить приложение. Результат будет тем же что и до этого, но теперь в Code-Behind файле чистота и порядок!

Заключение

Вряд ли созданный проект можно считать основой для конкретного приложения. Это просто начало начал. Для того чтобы проект стал проектом надо проделать еще кучу дополнительных телодвижений для подготовки инфраструктуры решения (solution). А вот количество телодвижений и их суть, уже зависит от того, какой тип приложения вы хотите построить: Модульное приложение, MDI-приложение, SDI-приложение и так далее. Одно могу сказать, что PRISM предоставляет все эти возможности.

Проект можно взять на GitHub

Комментарии к статье (2)

01.05.2017 18:29:59 Данил
А как инжектить интерфейсы во ViewModel ?
02.05.2017 4:13:09 Calabonga
Данил,

Для этого надо зарегистрировать ViewModel'ы в контейнере и использовать конструкцию PRISM, которая автоматически внедрит ViewModel во View:
prism:ViewModelLocator.AutoWireViewModel="True"