Содержание
До этого момента мы особо не задавались вопросом того, как спроектировать наше приложение, чтобы в дальнейшем его было легко поддерживать и вносить изменения и для демонстрации тех или иных возможностей .NET MAUI максимум, что мы использовали — это меняли разметку XAML страницы и создавали обработчики событий в файлах отдельного кода. Такой подход вполне может использоваться, однако, по мере развития проекта, роста его возможностей, появления в структуре проекта новых сущностей код нашего приложения станет трудно поддерживаемым, так как бизнес-логика приложения и пользовательский интерфейс станут настолько тесно связанными, что полне может оказаться, что переписать весь код с нуля будет проще, чем поддерживать существующий. Шаблон (паттерн) проектирования MVVM позволяет разделить структуру проекта на три части — модель (Model), модель представления (ViewModel) и представление (View).
Общие сведения о шаблоне MVVM
Шаблон MVVM можно представить в виде следующей схемы:

Представления (View)
Представление отвечает за определение структуры, макета и внешнего вида того, что пользователь видит на экране. В идеальном случае, каждое представление определяется в XAML с ограниченным кодом, который не содержит бизнес-логику. Однако в некоторых случаях код программной части может содержать логику пользовательского интерфейса, которая реализует визуальное поведение, которое трудно выразить в XAML, например анимации.
Модель представления (ViewModel)
Модель представления реализует свойства и команды, к которым представление может привязать данные, и уведомляет представление о любых изменениях состояния с помощью событий уведомлений об изменении. Свойства и команды, предоставляемые моделью представления, определяют функциональные возможности, предоставляемые пользовательским интерфейсом (логику), а представление определяет способ отображения этой функции.
Модель представления также отвечает за координацию взаимодействия представления с любыми необходимыми классами моделей.
Каждая модель представления предоставляет данные из модели в форме, которую представление может легко использовать. Для этого модель представления иногда выполняет преобразование данных. Размещение этого преобразования данных в модели представления является хорошей идеей, так как она предоставляет свойства, к которым может привязаться представление. Например, модель представления может объединить значения двух свойств, чтобы упростить отображение в представлении.
Модель
Классы модели — это не визуальные классы, которые содержат какие-либо данные приложения. Обычно, модель рассматривается как модель домена приложения, которая включает модель данных вместе с бизнес-логикой и логикой проверки.
Реализация MVVM в .NET MAUI
Простота и легкость реализации шаблона MVVM в приложениях .NET MAUI стала возможной, благодаря встроенному механизму привязки. Как правило, свойству BindingContext
визуального элемента (View) присваивается объект, реализующий в приложении модель представления (ViewModel), которая, в свою очередь, взаимодействует с различными моделями приложения.
Пример проекта с MVVM
Чтобы продемонстрировать применение этого архитектурного шаблона в приложении .NET MAUI создадим новый проект, добавим в него папку Models и разместим в этой папке класс:
public class Project { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public string Author { get; set; } }
Этот класс будет выступать в качестве модели. Теперь создадим модель представления. Для этого добавим в проект папку ViewModels и разместим в нем следующий класс:
public class ProjectViewModel : INotifyPropertyChanged { private Project project = new() { Id = 1, Name = "MVVM в .NET MAUI", Description = "Изучаем применение шаблона MVVM в .NET MAUI", Author = "Vlad (csharp.webdelphi.ru)", }; public string Name { get => project.Name; set { if (project.Name != value) { project.Name = value; OnPropertyChanged(); } } } public int Id { get => project.Id; set { if (project.Id != value) { project.Id = value; OnPropertyChanged(); } } } public string Description { get => project.Description; set { if (project.Description != value) { project.Description = value; OnPropertyChanged(); } } } public string Author { get => project.Author; set { if (project.Author != value) { project.Author = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler? PropertyChanged; public void OnPropertyChanged([CallerMemberName] string prop = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } }
Этот класс и выступает в качестве ViewModel — он получает данные из модели и предоставляет их в представление в удобном для использования виде — настраивает уведомления об изменении свойств и, при необходимости, модифицирует данные модели.
Третий компонент — представление (View). В качестве представления у нас будет выступать страница MainPage
. Теперь нам осталось только связать все три компонента воедино. Для начала, изменим C#-код страницы следующим образом:
using MvvmExample.ViewModels; namespace MvvmExample { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); BindingContext = new ProjectViewModel(); } } }
здесь мы присваиваем в конструкторе свойству BindingContext
объект ProjectViewModel
. Теперь в XAML мы можем отображать данные объекта, например, так:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MvvmExample.MainPage"> <ScrollView> <VerticalStackLayout> <Label Text="Название проекта" /> <Entry Text="{Binding Name}" FontAttributes="Bold" /> <Label Text="Описание" /> <Entry Text="{Binding Description}" FontAttributes="Bold" /> <Label Text="Автор" /> <Entry Text="{Binding Author}" FontAttributes="Bold" /> </VerticalStackLayout> </ScrollView> </ContentPage>
Теперь все три части связаны и можно проверить результат. Запустите приложение и убедитесь, что в элементах Entry
отображаются данные по проекту:
При написании кода XAML вы могли заметить, что редактор XAML «ругается» на то, что не может определить свойство привязки:
хотя приложение прекрасно работает. Чтобы избавиться от таких сообщений, достаточно добавить для страницы атрибут x:DataType
следующим образом:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MvvmExample.MainPage" xmlns:local ="clr-namespace:MvvmExample.ViewModels" x:DataType="local:ProjectViewModel"> <ScrollView> <VerticalStackLayout> <Label Text="Название проекта" /> <Entry Text="{Binding Name}" FontAttributes="Bold" /> <Label Text="Описание" /> <Entry Text="{Binding Description}" FontAttributes="Bold" /> <Label Text="Автор" /> <Entry Text="{Binding Author}" FontAttributes="Bold" /> </VerticalStackLayout> </ScrollView> </ContentPage>
Теперь никаких предупреждений не будет.
Передача значения BindingContext через конструктор
Мы уже знаем, что из себя представляет внедрение зависимостей и то как регистрировать сервисы и запрашивать их. Поэтому для нас не составит труда предоставить нашу модель представления в качестве сервиса. Добавим в MauiProgram.cs следующую строку:
using Microsoft.Extensions.Logging; using MvvmExample.ViewModels; namespace MvvmExample; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); builder.Services.AddSingleton<ProjectViewModel>();//регистрируем сервис #if DEBUG builder.Logging.AddDebug(); #endif return builder.Build(); } }
Теперь модель представления выступает в качестве сервиса и так как этот сервис необходим для работы представления, то мы можем запросить его в конструкторе класса, например, так:
public partial class MainPage : ContentPage { public MainPage(ProjectViewModel model) { InitializeComponent(); BindingContext = model; } }
В этом случае, .NET MAUI автоматически запросит необходимый сервис из контейнера DI и вернет его в параметре model
конструктора. Такой подход связывания View и ViewModel является достаточно распространенным и мы его, в дальнейшем, будем использовать.
Итого
Архитектурный шаблон MVVM позволяет отделить бизнес-логику приложения от интерфейса. В .NET MAUI шаблон достаточно легко реализуется, благодаря свойству BindingContext
визуальных компонентов. Одним из способов передачи значения в BindingContext
является использование ViewModel в качестве сервисов.