Миграция с Xamarin.Forms на .NET MAUI: полное руководство на 2026 год

Подробное руководство по миграции с Xamarin.Forms на .NET MAUI в 2026 году. Примеры кода, пошаговые инструкции: от структуры проекта и замены рендереров на обработчики до настройки DI и тестирования.

Введение: почему миграция на .NET MAUI неизбежна

1 мая 2024 года Microsoft официально прекратила поддержку всех SDK Xamarin, включая Xamarin.Forms. Проще говоря, ваши Xamarin-приложения больше не получают обновлений безопасности, исправлений багов и совместимости с новыми версиями ОС. Для команд, которые всё ещё сидят на Xamarin.Forms, миграция на .NET MAUI — это не вопрос «если», а вопрос «когда».

Я сам прошёл через миграцию нескольких коммерческих проектов, и честно скажу — это не так страшно, как кажется на первый взгляд. Да, работы немало, но при грамотном подходе всё вполне управляемо.

.NET MAUI (Multi-platform App UI) — эволюционный наследник Xamarin.Forms. Он предлагает единую модель проекта, встроенную поддержку внедрения зависимостей, новую архитектуру обработчиков (Handlers) вместо рендереров, улучшенную производительность и поддержку .NET 10. В этом руководстве мы подробно разберём каждый этап — от подготовки и планирования до финального тестирования и деплоя.

Итак, поехали.

1. Оценка текущего проекта и планирование миграции

1.1 Аудит зависимостей

Прежде чем бросаться переписывать код, проведите тщательный аудит проекта. Начните с инвентаризации всех NuGet-пакетов и сторонних библиотек:

// Используйте CLI для анализа зависимостей
dotnet list package --outdated
dotnet list package --vulnerable

Составьте таблицу совместимости для каждого пакета. Это скучная, но очень важная работа — пропустив этот шаг, вы рискуете застрять на середине миграции:

  • Полностью совместимые — пакеты, уже поддерживающие .NET MAUI (например, CommunityToolkit.Mvvm, Newtonsoft.Json)
  • Требуют обновления — пакеты с .NET MAUI-версиями (например, Syncfusion, Telerik)
  • Требуют замены — устаревшие пакеты без MAUI-поддержки, для которых нужно найти альтернативы
  • Собственные рендереры — кастомные рендереры, которые придётся переписать в обработчики

1.2 Подготовка Xamarin.Forms проекта

Для максимально гладкой миграции выполните подготовительные шаги ещё в рамках Xamarin.Forms:

  1. Обновите Xamarin.Forms до версии 5.0 или выше
  2. Переведите проект на .NET Standard 2.0 или выше
  3. Замените устаревшие API (MessagingCenter → WeakReferenceMessenger из CommunityToolkit)
  4. Удалите неиспользуемые зависимости
  5. Убедитесь, что все тесты проходят

Серьёзно, не пренебрегайте этим этапом. Чем чище будет ваш Xamarin-проект перед миграцией, тем проще будет весь процесс.

1.3 Выбор стратегии миграции

Тут есть три основных пути:

  • Автоматическая миграция — с помощью .NET Upgrade Assistant или GitHub Copilot Agent в Visual Studio 2026
  • Ручная пошаговая миграция — преобразование каждого проекта вручную с полным контролем
  • Создание нового проекта — создание .NET MAUI проекта с нуля и постепенный перенос кода

Для большинства проектов среднего размера я бы рекомендовал комбинированный подход: используйте Upgrade Assistant для первоначального преобразования, а затем вручную доработайте результат. Это экономит кучу времени на рутине, но при этом оставляет вам полный контроль над важными моментами.

2. Преобразование структуры проекта

2.1 Переход на SDK-стиль проекта

Первый и самый фундаментальный шаг — переход с классического формата .csproj на SDK-стиль. Вот как это выглядит на практике:

Было (Xamarin.Forms):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"
         DefaultTargets="Build"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Xamarin.Forms" Version="5.0.0.2578" />
    <PackageReference Include="Xamarin.Essentials" Version="1.8.1" />
  </ItemGroup>
</Project>

Стало (.NET MAUI):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
    <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">
      $(TargetFrameworks);net10.0-windows10.0.19041.0
    </TargetFrameworks>
    <OutputType>Exe</OutputType>
    <RootNamespace>MyApp</RootNamespace>
    <UseMaui>true</UseMaui>
    <SingleProject>true</SingleProject>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

Заметьте, насколько проще стал файл проекта. Это одна из тех вещей, которые сразу чувствуются после миграции.

2.2 Объединение платформенных проектов

В Xamarin.Forms типичное решение включало отдельные проекты для каждой платформы (MyApp.Android, MyApp.iOS). В .NET MAUI всё живёт в одном проекте, а платформенно-специфичный код лежит в папке Platforms:

MyApp/
├── Platforms/
│   ├── Android/
│   │   ├── AndroidManifest.xml
│   │   ├── MainActivity.cs
│   │   └── MainApplication.cs
│   ├── iOS/
│   │   ├── AppDelegate.cs
│   │   ├── Info.plist
│   │   └── Program.cs
│   ├── MacCatalyst/
│   │   └── ...
│   └── Windows/
│       └── ...
├── Resources/
│   ├── Images/
│   ├── Fonts/
│   └── Raw/
├── Views/
├── ViewModels/
├── Services/
├── MauiProgram.cs
└── App.xaml

Перенесите платформенно-специфичный код из отдельных проектов в соответствующие папки внутри Platforms. Файлы с условной компиляцией также можно размещать в общих папках с использованием суффиксов:

Services/
├── DeviceService.cs              // Общий интерфейс
├── DeviceService.Android.cs      // Android-реализация
└── DeviceService.iOS.cs          // iOS-реализация

Мне такой подход нравится больше, чем размещение всего в Platforms — код логически группируется по функциональности, а не по платформе.

3. Обновление пространств имён и XAML

3.1 Замена пространств имён C#

Основное изменение — замена всех пространств имён Xamarin на соответствующие в .NET MAUI. Вот полная таблица замен:

// Основные замены
Xamarin.Forms                    → Microsoft.Maui.Controls
Xamarin.Forms.Xaml               → Microsoft.Maui.Controls.Xaml
Xamarin.Essentials               → Microsoft.Maui.Devices (и подпространства)
Xamarin.Forms.Maps               → Microsoft.Maui.Controls.Maps
Xamarin.Forms.DualScreen          → Microsoft.Maui.Controls.Foldable
Xamarin.Forms.PlatformConfiguration → Microsoft.Maui.Controls.PlatformConfiguration

Для автоматической замены отлично подходят регулярные выражения. Можно сделать это из командной строки:

# Массовая замена пространств имён с помощью sed
find . -name "*.cs" -exec sed -i '' \
  's/using Xamarin.Forms/using Microsoft.Maui.Controls/g' {} +

find . -name "*.cs" -exec sed -i '' \
  's/using Xamarin.Essentials/using Microsoft.Maui.Devices/g' {} +

3.2 Обновление XAML-файлов

Во всех XAML-файлах нужно заменить пространство имён по умолчанию:

Было:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

Стало:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

Не забудьте также проверить, что все локальные пространства имён (xmlns:local, xmlns:viewmodels) указывают на обновлённые namespace вашего проекта.

4. Миграция точки входа приложения

4.1 Замена App.xaml.cs

В Xamarin.Forms точкой входа был класс App, наследующий Application. В .NET MAUI появляется новый файл MauiProgram.cs — это теперь основная точка конфигурации:

// MauiProgram.cs — новая точка входа в .NET MAUI
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");
            });

        // Регистрация сервисов (замена DependencyService)
        builder.Services.AddSingleton<INavigationService, NavigationService>();
        builder.Services.AddSingleton<IApiService, ApiService>();
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<MainPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

Если вы работали с ASP.NET Core, то паттерн покажется знакомым. По сути, Microsoft унифицировала подход к конфигурации приложений — и это большой плюс.

4.2 Обновление App.xaml.cs

Класс App становится значительно проще, так как конфигурация перенесена в MauiProgram:

// Было (Xamarin.Forms)
public class App : Xamarin.Forms.Application
{
    public App()
    {
        InitializeComponent();

        DependencyService.Register<IApiService, ApiService>();
        MainPage = new NavigationPage(new MainPage());
    }
}

// Стало (.NET MAUI)
public class App : Microsoft.Maui.Controls.Application
{
    public App()
    {
        InitializeComponent();
        MainPage = new AppShell();
    }
}

5. Замена DependencyService на встроенное внедрение зависимостей

5.1 Удаление атрибутов DependencyService

Это, пожалуй, одно из самых приятных изменений. В Xamarin.Forms для регистрации платформенных сервисов использовался (скажем прямо, довольно неуклюжий) атрибут [assembly: Dependency]. В .NET MAUI он полностью заменяется нормальным DI-контейнером на основе Microsoft.Extensions.DependencyInjection.

Было (Xamarin.Forms):

// Интерфейс
public interface IDeviceOrientationService
{
    DeviceOrientation GetOrientation();
}

// Android-реализация
[assembly: Dependency(typeof(DeviceOrientationService))]
namespace MyApp.Droid.Services
{
    public class DeviceOrientationService : IDeviceOrientationService
    {
        public DeviceOrientation GetOrientation()
        {
            // Платформенная логика
            var windowManager = Android.App.Application.Context
                .GetSystemService(Context.WindowService) as IWindowManager;
            // ...
        }
    }
}

// Использование
var orientation = DependencyService.Get<IDeviceOrientationService>()
    .GetOrientation();

Стало (.NET MAUI):

// Интерфейс (тот же)
public interface IDeviceOrientationService
{
    DeviceOrientation GetOrientation();
}

// Платформенная реализация с partial class
namespace MyApp.Services
{
    public partial class DeviceOrientationService : IDeviceOrientationService
    {
        public partial DeviceOrientation GetOrientation();
    }
}

// Platforms/Android/Services/DeviceOrientationService.cs
namespace MyApp.Services
{
    public partial class DeviceOrientationService
    {
        public partial DeviceOrientation GetOrientation()
        {
            var windowManager = Android.App.Application.Context
                .GetSystemService(Context.WindowService) as IWindowManager;
            // ...
        }
    }
}

// Регистрация в MauiProgram.cs
builder.Services.AddSingleton<IDeviceOrientationService, DeviceOrientationService>();

// Использование через конструкторное внедрение
public class MainViewModel
{
    private readonly IDeviceOrientationService _orientationService;

    public MainViewModel(IDeviceOrientationService orientationService)
    {
        _orientationService = orientationService;
    }
}

Конструкторное внедрение — это просто небо и земля по сравнению с DependencyService.Get. Код становится тестируемым, явным и предсказуемым.

5.2 Время жизни сервисов

При регистрации сервисов важно правильно выбрать время жизни:

  • AddSingleton<T>() — один экземпляр на всё время работы приложения (настройки, кэши, HTTP-клиенты)
  • AddTransient<T>() — новый экземпляр при каждом запросе (для ViewModel, страниц)
  • AddScoped<T>() — один экземпляр в рамках определённой области

На практике AddScoped в мобильных приложениях используется редко — в основном хватает Singleton и Transient.

6. Миграция кастомных рендереров на обработчики (Handlers)

6.1 Понимание архитектуры обработчиков

Обработчики — это, пожалуй, самое важное архитектурное изменение в .NET MAUI. В отличие от рендереров, обработчики:

  • Не создают дополнительный контейнерный элемент (привет, производительность)
  • Используют паттерн маппинга свойств (PropertyMapper) вместо OnElementPropertyChanged
  • Обеспечивают более слабую связность между кроссплатформенным и нативным кодом
  • Позволяют легко модифицировать существующие контролы без создания подклассов

6.2 Пример миграции кастомного Entry

Давайте разберём конкретный пример — миграцию кастомного Entry без рамки.

Было (Xamarin.Forms Custom Renderer):

// Кастомный контрол
public class BorderlessEntry : Entry { }

// Android Renderer
[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace MyApp.Droid.Renderers
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        public BorderlessEntryRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                Control.Background = null;
                Control.SetBackgroundColor(Android.Graphics.Color.Transparent);
                Control.SetPadding(0, 0, 0, 0);
            }
        }

        protected override void OnElementPropertyChanged(object sender,
            PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == Entry.TextProperty.PropertyName)
            {
                // Обработка изменения свойства
            }
        }
    }
}

Стало (.NET MAUI Handler):

// Кастомный контрол с интерфейсом
public interface IBorderlessEntry : IEntry { }

public class BorderlessEntry : Entry, IBorderlessEntry { }

// Handler
public partial class BorderlessEntryHandler : EntryHandler
{
    public BorderlessEntryHandler() : base(PropertyMapper) { }

    // PropertyMapper заменяет OnElementPropertyChanged
    public static new IPropertyMapper<IEntry, BorderlessEntryHandler> PropertyMapper =
        new PropertyMapper<IEntry, BorderlessEntryHandler>(EntryHandler.Mapper)
        {
            [nameof(IEntry.Background)] = MapBackground,
        };

    // Метод маппинга для Android
    public static void MapBackground(BorderlessEntryHandler handler, IEntry entry)
    {
#if ANDROID
        handler.PlatformView.Background = null;
        handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
        handler.PlatformView.SetPadding(0, 0, 0, 0);
#endif
    }
}

// Регистрация в MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<BorderlessEntry, BorderlessEntryHandler>();
});

Кода стало немного больше, но структура значительно чище. И, что важно, вы можете модифицировать маппинг извне, не создавая подклассов.

6.3 Быстрая модификация существующих обработчиков

А вот это — моя любимая фича. Во многих случаях не нужно создавать полноценный обработчик. .NET MAUI позволяет модифицировать поведение стандартных контролов буквально парой строк:

// В MauiProgram.cs — глобальная модификация всех Entry
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("BorderlessEntry",
    (handler, view) =>
    {
#if ANDROID
        handler.PlatformView.Background = null;
        handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
#elif IOS || MACCATALYST
        handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
#endif
    });

Элегантно, правда?

7. Обновление навигации

7.1 Переход на Shell

Если вы ещё не использовали Shell в Xamarin.Forms, миграция — отличный момент для перехода. Shell даёт единую модель навигации с поддержкой URI-маршрутизации:

<!-- AppShell.xaml -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MyApp.Views"
       x:Class="MyApp.AppShell">

    <FlyoutItem Title="Главная" Icon="home.png">
        <Tab Title="Лента">
            <ShellContent ContentTemplate="{DataTemplate views:HomePage}" />
        </Tab>
        <Tab Title="Профиль">
            <ShellContent ContentTemplate="{DataTemplate views:ProfilePage}" />
        </Tab>
    </FlyoutItem>

</Shell>
// Регистрация маршрутов
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        // Регистрация маршрутов для страниц, не входящих в визуальную иерархию
        Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
        Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage));
    }
}

// Навигация по маршрутам
await Shell.Current.GoToAsync(nameof(DetailPage), new Dictionary<string, object>
{
    { "ItemId", selectedItem.Id }
});

7.2 Передача параметров через атрибуты

// ViewModel с параметрами навигации
[QueryProperty(nameof(ItemId), "ItemId")]
public partial class DetailViewModel : ObservableObject
{
    [ObservableProperty]
    private int itemId;

    partial void OnItemIdChanged(int value)
    {
        // Загрузка данных при получении параметра
        LoadItemAsync(value);
    }
}

Работает чисто и предсказуемо. Единственный нюанс — для сложных объектов лучше передавать ID и загружать данные в целевой ViewModel, а не пытаться протолкнуть весь объект через параметры навигации.

8. Обновление ресурсов и ассетов

8.1 Единая система ресурсов

В .NET MAUI все ресурсы управляются из одного места. Перетащите изображения, шрифты и прочие ассеты в папку Resources:

<!-- В .csproj -->
<ItemGroup>
    <!-- Изображения автоматически масштабируются для каждой платформы -->
    <MauiImage Include="Resources\Images\*" />

    <!-- Шрифты -->
    <MauiFont Include="Resources\Fonts\*" />

    <!-- Splash-экран -->
    <MauiSplashScreen Include="Resources\Splash\splash.svg"
                       Color="#512BD4"
                       BaseSize="128,128" />

    <!-- Иконка приложения -->
    <MauiIcon Include="Resources\AppIcon\appicon.svg"
              ForegroundFile="Resources\AppIcon\appiconfg.svg"
              Color="#512BD4" />

    <!-- Сырые ассеты -->
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

Больше не нужно хранить отдельные версии изображений для Android (drawable-hdpi, drawable-xhdpi и т.д.) и iOS (@2x, @3x). .NET MAUI автоматически генерирует нужные размеры из SVG или растрового изображения с достаточным разрешением. Это экономит кучу времени и нервов.

8.2 Миграция стилей и тем

Стили и ресурсы из App.xaml переносятся практически без изменений — нужно только обновить пространства имён:

<!-- Resources/Styles/Styles.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

    <Style TargetType="Entry">
        <Setter Property="TextColor" Value="{AppThemeBinding Light=#1A1A1A, Dark=#FFFFFF}" />
        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light=#FFFFFF, Dark=#2D2D2D}" />
        <Setter Property="FontSize" Value="14" />
    </Style>

    <!-- Поддержка тёмной темы через AppThemeBinding -->
    <Color x:Key="PrimaryColor">#512BD4</Color>
    <Color x:Key="SecondaryColor">#DFD8F7</Color>

</ResourceDictionary>

9. Использование .NET Upgrade Assistant

9.1 Установка и запуск

Microsoft предоставляет .NET Upgrade Assistant — инструмент, который автоматизирует многие рутинные шаги миграции. В Visual Studio 2026 также можно использовать GitHub Copilot Agent для модернизации, но CLI-версия тоже отлично справляется:

# Установка .NET Upgrade Assistant
dotnet tool install -g upgrade-assistant

# Анализ проекта
upgrade-assistant analyze MyApp.sln

# Запуск миграции
upgrade-assistant upgrade MyApp.sln

Upgrade Assistant автоматически выполнит:

  • Преобразование файла проекта в SDK-стиль
  • Обновление целевого фреймворка
  • Замену пространств имён Xamarin на MAUI
  • Обновление совместимых NuGet-пакетов
  • Базовую адаптацию XAML-файлов

9.2 Что потребует ручной доработки

Автоматика не всесильна. После работы Upgrade Assistant вам всё равно придётся руками поправить:

  • Кастомные рендереры (преобразование в обработчики)
  • Платформенно-специфичный код в отдельных проектах
  • Устаревшие API (DependencyService, MessagingCenter)
  • Сторонние библиотеки без автоматической замены
  • Пользовательские Effects (замена на Platform Behaviors)

Не расстраивайтесь — даже частичная автоматизация экономит часы работы.

10. Обработка распространённых проблем миграции

10.1 Изменения в поведении Layout

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

  • StackLayout в .NET MAUI не добавляет дополнительных отступов по умолчанию. Если ваш UI «поехал», первым делом проверьте Spacing и Padding
  • Grid теперь строже обрабатывает определения строк и столбцов
  • Используйте VerticalStackLayout и HorizontalStackLayout вместо StackLayout для лучшей производительности
<!-- Было -->
<StackLayout Orientation="Vertical" Spacing="10">
    <Label Text="Заголовок" />
    <Entry Placeholder="Введите текст" />
</StackLayout>

<!-- Стало (оптимизированная версия) -->
<VerticalStackLayout Spacing="10">
    <Label Text="Заголовок" />
    <Entry Placeholder="Введите текст" />
</VerticalStackLayout>

10.2 MessagingCenter заменён

В .NET MAUI 10 класс MessagingCenter стал internal. Если вы ещё не перешли на WeakReferenceMessenger из CommunityToolkit.Mvvm — самое время:

// Установка пакета
// dotnet add package CommunityToolkit.Mvvm

// Определение сообщения
public class ItemUpdatedMessage : ValueChangedMessage<Item>
{
    public ItemUpdatedMessage(Item value) : base(value) { }
}

// Отправка сообщения
WeakReferenceMessenger.Default.Send(new ItemUpdatedMessage(updatedItem));

// Подписка на сообщение
WeakReferenceMessenger.Default.Register<ItemUpdatedMessage>(this, (recipient, message) =>
{
    var item = message.Value;
    // Обработка обновления
});

WeakReferenceMessenger, на мой взгляд, лучше MessagingCenter по всем параметрам — он типобезопасен, использует слабые ссылки и не вызывает утечек памяти.

10.3 Замена Effects на Platform Behaviors

// Было (Xamarin.Forms Effect)
public class ShadowEffect : RoutingEffect
{
    public ShadowEffect() : base("MyApp.ShadowEffect") { }
}

// Стало (.NET MAUI PlatformBehavior)
public partial class ShadowBehavior : PlatformBehavior<View>
{
    public static readonly BindableProperty RadiusProperty =
        BindableProperty.Create(nameof(Radius), typeof(float), typeof(ShadowBehavior), 10f);

    public float Radius
    {
        get => (float)GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }

#if ANDROID
    protected override void OnAttachedTo(View bindable, Android.Views.View platformView)
    {
        base.OnAttachedTo(bindable, platformView);
        platformView.Elevation = Radius;
    }
#elif IOS
    protected override void OnAttachedTo(View bindable, UIKit.UIView platformView)
    {
        base.OnAttachedTo(bindable, platformView);
        platformView.Layer.ShadowRadius = Radius;
        platformView.Layer.ShadowOpacity = 0.3f;
    }
#endif
}

11. Тестирование после миграции

11.1 Стратегия тестирования

После миграции критически важно провести тщательное тестирование. Не пропускайте этот этап, даже если «всё компилируется и вроде работает»:

  1. Юнит-тесты — запустите существующие тесты. Большинство должно работать после обновления пространств имён
  2. UI-тесты — проверьте визуальное отображение на всех целевых платформах
  3. Интеграционные тесты — убедитесь, что API, база данных и сторонние сервисы работают корректно
  4. Регрессионное тестирование — пройдите все основные пользовательские сценарии вручную

11.2 Пример юнит-теста

// xUnit проект для тестирования ViewModel
[Fact]
public async Task LoadItems_ReturnsExpectedData()
{
    // Arrange
    var mockService = new Mock<IApiService>();
    mockService.Setup(s => s.GetItemsAsync())
        .ReturnsAsync(new List<Item> { new Item { Id = 1, Name = "Тест" } });

    var viewModel = new MainViewModel(mockService.Object);

    // Act
    await viewModel.LoadItemsCommand.ExecuteAsync(null);

    // Assert
    Assert.Single(viewModel.Items);
    Assert.Equal("Тест", viewModel.Items[0].Name);
}

Ключевые области для проверки (не ленитесь, проверьте каждый пункт):

  • Навигация между страницами (прямая, обратная, глубокие ссылки)
  • Жизненный цикл приложения (фоновый режим, восстановление)
  • Платформенные разрешения (камера, геолокация, файлы)
  • Push-уведомления
  • Локализация и RTL-макеты
  • Адаптивный дизайн на разных размерах экрана
  • Производительность прокрутки и анимаций

12. Оптимизация производительности после миграции

12.1 Compiled Bindings

Обязательно включите компилируемые привязки — они дают заметный прирост производительности биндинга:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
             x:DataType="viewmodels:MainViewModel">

    <!-- Привязки компилируются, ошибки обнаруживаются на этапе сборки -->
    <Label Text="{Binding UserName}" />
    <CollectionView ItemsSource="{Binding Items}">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Item">
                <Label Text="{Binding Name}" />
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>

</ContentPage>

Бонус: ошибки в биндингах теперь ловятся при компиляции, а не в рантайме. Это одна из тех фич, к которым быстро привыкаешь и уже не можешь без них.

12.2 Native AOT (экспериментально)

В .NET 10 появилась экспериментальная поддержка Native AOT для мобильных платформ. Это значительно сокращает время запуска приложения:

<PropertyGroup>
    <PublishAot>true</PublishAot>
    <!-- Включение для iOS -->
    <MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup>

Будьте осторожны с AOT — фича ещё экспериментальная, и не все библиотеки с ней дружат. Тестируйте тщательно.

13. Пошаговый чек-лист миграции

Для удобства — итоговый чек-лист. Распечатайте его или скопируйте в свой таск-трекер:

  1. Провести аудит зависимостей и составить таблицу совместимости
  2. Обновить Xamarin.Forms до версии 5.0+
  3. Перевести проект на .NET Standard 2.0+
  4. Создать ветку для миграции в системе контроля версий
  5. Запустить .NET Upgrade Assistant для первоначального преобразования
  6. Преобразовать .csproj в SDK-стиль с мульти-таргетингом
  7. Объединить платформенные проекты в единую структуру
  8. Заменить пространства имён (C# и XAML)
  9. Создать MauiProgram.cs и перенести конфигурацию
  10. Заменить DependencyService на встроенный DI
  11. Мигрировать кастомные рендереры на обработчики
  12. Заменить Effects на PlatformBehavior
  13. Заменить MessagingCenter на WeakReferenceMessenger
  14. Обновить систему навигации (Shell)
  15. Перенести ресурсы в единую папку Resources
  16. Обновить стили и темы
  17. Запустить и исправить ошибки компиляции
  18. Провести юнит-тестирование
  19. Провести UI-тестирование на всех целевых платформах
  20. Провести регрессионное тестирование
  21. Профилировать производительность
  22. Обновить CI/CD пайплайн
  23. Развернуть в тестовую среду
  24. Опубликовать в App Store и Google Play

Заключение

Миграция с Xamarin.Forms на .NET MAUI — это серьёзный, но необходимый шаг для любого .NET-проекта мобильной разработки. Да, процесс может показаться масштабным. Но при правильном планировании и пошаговом подходе он вполне управляем — проверено на собственном опыте.

Что вы получите после миграции:

  • Активная поддержка — регулярные обновления безопасности и совместимости
  • Улучшенная производительность — обработчики вместо рендереров, Compiled Bindings, Native AOT
  • Единая структура проекта — проще управлять и поддерживать
  • Встроенный DI — стандартная модель внедрения зависимостей без сторонних библиотек
  • Современный .NET — доступ ко всем возможностям .NET 10 и C# 13
  • Совместимость с новыми ОС — поддержка Android API 36+, iOS 17+ и актуальных требований магазинов

Не откладывайте миграцию. Чем дольше вы остаётесь на неподдерживаемом Xamarin, тем больше копится технический долг и растут риски безопасности. Начните с аудита, выберите стратегию и двигайтесь шаг за шагом.

Удачной миграции!

Об авторе Editorial Team

Our team of expert writers and editors.