Содержание
До этого момента мы особо не задавались вопросом того, как спроектировать наше приложение, чтобы в дальнейшем его было легко поддерживать и вносить изменения и для демонстрации тех или иных возможностей .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 в качестве сервисов.
